OCaml: What would be the idiomatic way to add an element using Core Set?

381 views Asked by At

In standard library, add function has the following signature:

val add : elt -> t -> t

So I can add elements with pipeline operator:

Set.empty |> add elt1 |> add elt2

However, when I switch to Core, I notice that the signature for add has become to:

val add : ('a, 'cmp) t -> 'a -> ('a, 'cmp) t

Now set becomes the first parameter. The old pipeline-style does not apply to it anymore.

What would be the idiomatic way to add an element using Core Set?

3

There are 3 answers

0
Michaël Le Barbier On BEST ANSWER

Do not use the pipeline to obfuscate function application!

The pipeline has good reasons to exist, namely to build pipelines, but in your example, you are merely abusing and obfuscate your code.

If you just want to add two elements to a set, just write

Set.(add (add empty a) b)

It is perfectly clean an legible. If you have more elements to add, you can improve readability by using a fold-function:

List.fold_left Set.add Set.empty [a; b; c; d; e; f]

We could speculate that Jane Street changed the signature of Set.add precisely to use it with List.fold_left which is tail-recursive, while the List.fold_right that should be used with the sets from the standard library is not tail recursive.

0
Jeffrey Scofield On

After playing around for a few minutes, the best I can think of is to use flip to reverse the parameter order.

let flip f a b = f b a

Then you can write:

Set.empty |> flip add elt1 |> flip add elt2

Last time I checked, flip was available in Core as Fn.flip.

(Generally speaking, no parameter order is best in all cases. You might decide eventually that the Core order is really great for some things.)

0
Ashish Agarwal On

Jane Street explained this design choice in this blog post as the t comes first rule. Basically the point is there is no good choice. Sometimes it is useful to have t come first and sometimes last. Perhaps the main benefit of their choice is that it is a decision. As they state in their introduction, sometimes the benefit of a decision is to avoid wasting time thinking about the matter all the time.

Answering your question more directly, I'd say there is no idiomatic solution for this. Write whatever seems intuitive to you. Some advice has been given to you in other answers. Don't abuse the pipe operator. Use fold if there are more items to add. I would avoid flip; you obfuscate the code without making it more concise (but I do use it sometimes too).

Finally, note that Core's additional design choice to use labeled arguments often resolves the matter completely. For example, their List.fold also takes t first but the other arguments are labeled. Thus, you can actually put t wherever you like, making List.fold usable both with pipes and without. So one could ask why they didn't make the signature of Set.add be t -> elt:elt -> t. Well, labels are also an extra overhead; they cause you to type more characters. It would be extreme to use them everywhere, and they decided this function was better without labels.