`t` comes first or comes last when designing a library

Hi, I am designing a library around data structures, so the design question is would t comes as the first argument or last be a better choice? Is there any deep reason behind your argument, would love to hear about your opinions.

  1. t comes last, good for pipe |>
  2. t comes first, similar to other languages, like golang method

Hi Bob. I thought this was a fairly settled argument in favour of option 1.

Let me explain why I think preserving pipe-ability is a good idea. In object-oriented languages, you can write code in a fluent style:

Quux quux = foo
  .bar(1)
  .baz(true)
  .build();

In mainstream languages fluent style is considered good design. With piping we also have a fluent style:

let quux = foo
  |> bar 1
  |> baz true
  |> build ()

In general I think preserving left-to-right readability is a great idea.

6 Likes

I recommend making t an anonymous argument, and naming all other arguments. Just like option (1), this allows pipeing, and it makes code much easier to read. This is the style adopted in Base/Core.

7 Likes

I am not sure t comes last is best practice, see my comments in this issue:

I tend to agree with you, but will it make code too verbose and less composable?

A function with one anonymous argument and several named arguments is just as composable as a function with several anonymous arguments: the former composes on the unique anonymous argument, the latter on the final anonymous argument. (I would challenge you to come up with a convincing practical counter example.)

Regarding verbosity, my experience is that there is little to no difference. For example, functions are always named ~f in Base/Core, a very concise but descriptive name. Moreover, OCaml has some nice syntax sugar for the comon case that the name of the argument matches a variable in scope: you can write foo x ~default instead of foo x ~default:default. Finally, I would argue that the more readable code allows for shorter variable names/less documentation in practice, so it’s really a win-win.

2 Likes

By the way, with the 1 anonymous argument convention you also don’t need your .| operator.

You could be interesting by the convention from the Edison library in Haskell which took advises from the Chris Okazaki’s book about purely functional data structures.

And, from hat I know about standard library, your second convention is used when we manipulate a mutable data-structure (like Hashtbl). Containers seems to follow this convention.

I prefer both to diff mutable data-structure and immutable data-structure to allow the user to compose as you said the second one but disallow for the first one and use a sequence: add t key value; add t key value; ...

3 Likes

I would say it depends on how your functions are intended to be used. From my point of view, the general form of a function’s type is a -> b, i.e. it is something that transforms an object of type a to an object of type b: a morphism. So when I see a function with the type a -> b -> c, I see it as a family of morphisms from type b to type c parametrized by a value of type a. Therefore, if you want a family of morphisms that operate on your type t, you put it last, but if it is your type that parameterized a family of morphisms (as with regex for instance) you put it first.

If I take this two examples from the standard lib:

List.map;;
- : ('a -> 'b) -> 'a list -> 'b list = <fun>

List.nth;;
- : 'a list -> int -> 'a = <fun>

I consider the first one has a good type: we transform a morphism from a to b to a morphism from a list to b list (the type constructor list is a functor, and map is the fmap of the functor type classes in Haskell). But for the second one, I would gave it the type int -> 'a list -> 'a considering this is only a generalization of hd : 'a list -> 'a (== List.nth 1).

7 Likes

My general method is to stick to one approach until I realize it makes more sense to go the other way, then half-heartedly switch over some of the functions, and eventually end up with a mess. Works for me!

10 Likes

I really prefer the method they use at janestreet with t being an anonymous argument with any other arguments being named. You get the added benefit of self documentation.

3 Likes

One language I feel gets this right is Elixir. If the function is on the right side of the |> then the result of the left side is injected as the first argument of the function.