Reading OCAML type signatures

In using the Cmdliner package I see the following usage:

Term.(const arg1 arg2 ), – where arg1 is typeCmdliner.Term.t

The type of Term. is
'a => Cmdliner.Term.t('a)

In the Term module documentation it says:

type +'a t - which I have no idea how to read.

So what is happening? What this Term.(xxx) doing. I is not a function call, What in the module Term is receiving the expression, and what is it doing with that expression? (this reminds me of Monads in Haskell, but I know Monads and Functors are not used that often in OCAML).

Second question:

Sometimes I see things like

'a string list

I understand 'a list and string list (parameterized types) but what does it mean when they are cascaded and what is the process for unwrapping such an object?

'a => Cmdliner.Term.t('a)

This looks like Reason syntax to me, which is different from the OCaml syntax. I believe the OCaml equivalent would be:

'a -> 'a Cmdliner.Term.t

The type declaration in Term says:

type +'a t

This is a variance declaration, saying that the 'a type parameter is covariant. See the manual for more info (search for “variance”).

For your second question,

'a string list is not a possible type, because string does not have any type parameters. You might see something like 'a list list, which is a list of (list of 'a). You can think about it like ('a list) list.

Ok, so what is

Term.( ) doing (why the period after Term, and what is the parenthesis about?

Not sure how you got that

Module.(expr) is the same as let open Module in expr

1 Like

Assuming you found Term.(bla bla) in code, that is a shorthand for the syntactic form (bla bla) but with module Term open. So

List.(hd (rev l))

is the same as

List.hd (List.rev l)

You see this a lot in, for instance, Fmt module usage.

That’s not what the first snippets says.

'a => Cmdliner.Term.t('a)

(notice the t before the parens). That’s how Reason denotes type parameters. It is completely unrelated to the local module open syntax, which would not have the t there.

I got the signature

'a => Cmdliner.Term.t('a)

from VSCode with the ReasonML plugin. the file is pure OCAML.ml but the reason plugin handles both ReasonML and OCAML. It just reports signatures in reasonML format. Sorry for the confusion.

So to recap

Term.(blah1 $ blah2 $ blah3)

Is syntax sugar for

Term.blah1 Term.$ Term.blah2 Term.$ Term.blah3

or in my case:

Term.(const oCommander $ const ()),

is

Term.const oCommander Term.$ Term.const ()

Ah, yes, that’s correct. Sorry, I misunderstood which snippet you were talking about! My bad!

The next part is right, but I would quibble with this: it doesn’t have the same effect as putting Term. in front of every identifier, because the module Term might not contain every identifier in the expression. To take your example, oCommander is coming from somewhere other than Term, which is why you correctly do not write Term.oCommander when rewriting the expression.

It’s not purely syntactic sugar, but rather depends on the semantic property of what of what is in Term.

As @SkySkimmer said, it is however syntactic sugar for

begin
  let open Term in
  blah1 $ blah2 $ blah3
end

(Instead of begin and end parens can also be used to delimit the scope of the local open)

1 Like