Why constructors are not curried?

Hello, I am one of those who are confused when learning about multi-arg constructors and constructors on tuples. It seems to make sense to me after reading some discussions. However, I still have a few questions. For example, in the following program,

type t = A of int * string | B of (int * string) | C of int

let x = A (1, "a")
let y = B (1, "a")
let z = C 1
  1. Both x and y are correct. So it seems to me the syntax is ambiguous whether the construction A (1, "a") is constructing a multi-arg constructor or a constructor of tuple. So why not use A (1, "a") and B ((1, "a")) to distinguish the two cases?
  2. When the payload of constructor is a single value, we can construct it using curried form like C 1, but we cannot do that for the other cases. So why not use the curried form uniformly? Then we can use A 1 "a" and B (1, "a") to solve the previous problem.

I’ve seen the “revised syntax” mentioned somewhere: Why can't I use constructors as functions? and Why can't I use constructors as functions?. In the “revised syntax”, the constructors are curried which seem to solve the problems. So I was also wondering about the “revised syntax” is really about? Are they going to be merged into future version of OCaml?

Thanks for your help!

1 Like

They are also not first-class, I realized…

Yes, the syntax is the same in both cases.

In perfect hindsight, this would have been a better syntax choice indeed, but alas, now it is too late to change: it would break every single OCaml program in existence.

The revised syntax was an alterantive syntax for OCaml that began as part of the (now deprecated) Camlp4/Camlp5 projects. While it solved a number of issues with the classical syntax, it never got enough traction to be adopted, and eventually interest on it faded away.

For more on this topic you may enjoy going through the following two issues and the discussions linked therein:

Cheers,
Nicolas

2 Likes

I am not sure. Constructor application is a pure multi-argument application with specific rules. It is really not clear that using the injection between A x B -> C and A -> B -> C is a good choice in this case. Typically, with the syntactic arity change in 5.2, the compiler is rather (silently) moving in the opposite direction for functions too.

You can see the unpacked memory representation for constructor with multiple arguments as a type-directed optimisation. From this point of view, the syntax is not really ambiguous between x and y, since the optimised representation is not a syntactic concern.

Similarly, it seems better to preserve the fact ((x,y)) is always equivalent to (x,y). Moreover, don’t forget that tuples don’t require parentheses:

let l = [ 1 , 2 ]

is valid and the one-tuple in C 1 is just the specialization of this rule for one-tuple.

1 Like

That’s a surprising opinion and I’m really curious about the downsides of that approach, because to me it seems like purely an improvement:

  • makes the types syntax more uniform and less surprising, knowledge about how to write function types carries over to constructor types.
    • currently it’s not apparent that to have a tuple argument you need the parens, and without that, you can’t access a * b as an actual tuple
    • this is also made more apparent with the introduction of gadts syntax, where a constructor type decl is even more like a function’s and less its own thing (losing of for example)
  • makes constructor arity visually unambiguous (even if it is unambiguous to the compiler thanks to the type decl…)
  • makes the question of treating constructors as functions rather straightforward, and not like the PR #9005 discussion
  • makes having operator constructors a trivial matter (because normal ocaml operators are also curried)
    • this could’ve gone the other way if operators were defined tupled though
  • more concise declarations

I realize all these have simple solutions, or aren’t much of a problem to begin with, but if we’re discussing purely language design with the benefit of hindsight, wouldn’t curried constructors be a better design?

2 Likes

Being able to do match ... with C _ -> ... is convenient when C has many arguments (ie instead of C (_,_,...,_,_)) and when its arity changes between API versions, I’m not sure there would be a good alternative with curried constructors.

1 Like

you can imagine it’s the same thing, considering C _ is both 1-ary and n-ary anyway.

You are thinking of the syntax for type constructors, aren’t you? This is mostly unrelated to curryfication of constructor. Even so the most surprising part of the syntax for type constructor is the right-to-left evaluation order and the different syntax for tuples. The syntax could be made more regular with or with or without curryfication.

I agree with that point that this is an unfelicity of the language (but maybe a better fix would be to expose unboxed tuples?).

Now, you are switching to the syntax for variant constructors. I shall simply remark that GADTs successively use the tuple syntax:

type 'a t = X: 'a * 'b -> <fst:'a; snd:' b>.

That’s would still not be the case? Constructor applications are not function applications in OCaml.
Remember my previous example:

let some x = Some x
let x = some Fun.id
(* versus *)
let y = Some Fun.id

And there is also the question of inline records (or labelled constructor arguments?).

I still think it is debatable without having strong polymorphic functions and in presence of not-fully first class inline records which afford quite few niceties like

| C r -> r.a + r.z, C { r with l = 0; k = 1 }

But at the same time I agree that if those points of tension were removed, I would prefer to unify the function and constructor sides.

1 Like