Why don't we have a composition operator in Pervasives?

We have the ugly |> operator and the even ugglier @@ operator.
But, I want to have a compose operator:
let compose f g x = f (g x);;
val compose : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = .
To me, this would be more useful than the two other operators.
Now, we “just” need to agree on the notation for this operator and make it enter in the stdlib’s Pervasives module…
I propose <.>.

4 Likes

That’s questionable. Do you have any example ?

It is very useful in combinators:

List.map (f . g) x instead of List.map (fun x -> f (g x)) x

7 Likes

An operator I find really usefull is the composition from left to right:

let (>>) f g x = g ( f x)

It would be nice to have it along `|>’ in the pervasive module.

1 Like

Useful, yes. More useful, I’m not so sure. A compose function in stdlib is definitely nice to have.

I err on the no side of this. Just make it as compose is clearer IMO. Unless the symbol is well-defined (e.g. from math) or has historical significance (e.g. >>=) I’m not sure if introducing a new symbol operator out of nowhere is desirable. Open to be persuaded otherwise though.

Btw, Jane Street’s Base already has compose in their Fn module.

All the mathematical literature. :wink: What do you want to do with functions other than composing them (and apply them of course)? This is the base of categorical interpretation of lambda-calculus.

This diagram is the operator defined by @didier-wenzek:

let (>>) f g x = g (f x)

And as @Freyr666 said, it is very useful with combinators like iter or map:

List.map ( ( * ) 2 >> (+) 3) [1; 2; 3];;
- : int list = [5; 7; 9]

List.iter (string_of_int >> print_endline) [1; 2; 3];;
1
2
3
- : unit = ()

compare with:

[1; 2; 3]
|> List.map (( * ) 2)
|> List.map ((+) 3);;
- : int list = [5; 7; 9]

[1; 2; 3]
|> List.map string_of_int
|> List.iter print_endline;;
1
2
3
- : unit = ()
2 Likes

I guess we also don’t have this operator in batteries. :frowning:

Batteries has % for right-to-left composition and %> for left-to-right composition.

6 Likes

I don’t think this one is a good example.

The following is hardly longer, and much more readable in my view :

List.map ( fun x->2*x+3 ) [1; 2; 3];;
- : int list = [5; 7; 9]

A mathematician newcomer to Ocaml, for example, will immediately understand what fun x->2*x+3 means, but scratch his head over ( * ) 2 >> (+) 3.

True, string_of_int >> print_endline saves a few characters compared to fun i->print_endline(string_of_int i), but this is such a micro-optimization…

In both mathematics and computer science, “pure composition” is rarely encountered over more than two or three terms.

Which is why I don’t think it’s worth a complication in the Pervasives module.

1 Like

You’re right this is not a good example, not very readable.

Not sure, otherwise the |> combinator would not be used as much.

let poly1 x = 2 * x + 3
let poly2 x = 3 * x + 5

[1; 2; 3]
|> List.map poly1
|> List.map poly2
|> List.map string_of_int
|> List.iter print_endline;;
20
26
32
- : unit = ()

(* compared to *)
List.iter (poly1 >> poly2 >> string_of_int >> print_endline) [1; 2; 3];;
20
26
32
- : unit = ()

I think flambda can inline the first version to something equivalent to the second one (which is more efficient), but not the regular compiler.

2 Likes

I’d love to have this symbol in pervasives. Everyone ends defining their own version and it’s a pain. While I used to use |- (picked it up somewhere), I much prefer a single character if possible (like haskell’s . ) and % being available is perfect.

The problem with >> is that it’s commonly used in haskell monads, together with >>=. Even though OCaml can’t use >> due to its weird argument evaluation order (later things would be evaluated before earlier things), it would be convenient to define (>>), perhaps with a preprocessor. In any case, it’s closely associated with monads.

I dimly recall a discussion about this where a good reason for not having a composition operator was brought up. I cannot reproduce it precisely but I think it had to do with undesirable interaction with the value restriction or eta-expansion; i think there were performance considerations as well. Maybe someone can weigh in? It could also be that Flambda has removed some previous concerns?

“everyone end up defining their own” seems like an overstatement. We mostly just don’t use a composition operator in our ten million lines or so of OCaml code. All told, given |> and @@, adding more such operators adds only a little concision, and isn’t worth the loss to clarity and readability inherent with a profusion of infix operators, in my view.

6 Likes

As a pure aside, if everyone was using Unicode everywhere, the obvious name for a composition operator would be ∘ aka U+2218, which is the standard mathematical notation (i.e. “g ∘ f”).

It’s a great shame that, even though there is a vast array of very nice unicode symbols for common mathematical operations, we still are so often constrained to use rather ugly hacks with ASCII symbols to do what is so much more readable with unicode math symbols.

6 Likes

One of the reasons for that may be the prevalent use of named arguments in Core/Base: functions with named arguments generally don’t compose well in OCaml. While I agree that concision can be taken too far, often you can get rid of cruft and really clarify the algorithm with composition operators.

2 Likes

Well-written OCaml code tends to be very readable. Let’s keep it that way?
@kantian’s example actually illustrates nicely how things can get less readable in point free style.

5 Likes

I disagree. How does a function with two anonymous arguments compose any better than a function with one anonymous argument and one named argument? In both cases, you can either compose directly or partially apply and then compose.

In fact, I find that base/core function compose very well in practice. They usually have a single anonymous argument — the one you want to compose on in 95% of the cases — and name alll other arguments.

Perhaps that’s true – I don’t have enough experience with Core.

Anyway, I find that there is benefit in point-free style and composition. I try not to overuse it, but it’s a great little tool. It’d be a lot better, though, if it were standardized, so that anyone could immediately understand it, rather than having to learn a particular author’s convention.

4 Likes

I found that old discussion. It seems the discussion was just on the best associativity rule for performance:
https://sympa.inria.fr/sympa/arc/caml-list/2015-10/msg00077.html

I think that limited forms of point-free programming are great, and we already have standard idioms for it. But once you have >>=, >>|, |> and @@ in common use, I think you’re already verging on having too many. Personally, I’m sorry @@ was introduced.

As a practical matter, this isn’t that bad:

(fun x -> x |> f1 |> f2 |> f3)

and my experience is that it just isn’t common enough to justify a new bit of punctuation to make this a few characters shorter.

y

4 Likes