1

Learning OCaml, and wondering why the compiler thinks I'm providing zero arguments to a one-argument type constructor.

This OCaml:

open Core

module Json = Yojson.Safe.Util

type ticker = Ticker of string

let ticker_of_yojson s = 
  s |> Json.to_string |> Ticker |> Result.Ok

produces this error:

The constructor Ticker expects 1 argument(s)
but is applied here to 0 argument(s).

If I change it to this, wrapping the constructor calls in lambdas,

let ticker_of_yojson s = 
  s |> Json.to_string |> (fun x -> Ticker x) |> (fun x -> Result.Ok x)

it compiles. If I only wrap the Ticker constructor call in a lambda, I get the same zero arguments error regarding Result.Ok.

What is the compiler missing here that substituting with the lambdas resolves? Is there something else I can add such that the original version without the lambdas works?

I saw this other SO post that seems like a similar problem, but I can't connect the dots between the two situations: ocaml type constructor arguments

Thanks for your time - any help appreciated.

Jake
  • 2,852
  • 7
  • 32
  • 39

3 Answers3

3

An OCaml constructor is not a function, that's just how the language works. Syntactically a unary constructor wants an expression after it. So you can't use a constructor by itself as a function, you need to wrap it in a lambda exactly as you say.

A binary (or ternary, etc.) constructor wants a parenthesized list of comma-separated expressions after it. So you also can't partially apply a constructor in OCaml.

Other languages (notably Haskell) treat a constructor as any other (curried) function and it works out really nicely. But it's probably too late to change OCaml at this point.

Jeffrey Scofield
  • 65,646
  • 2
  • 72
  • 108
1

OCaml makes a difference between function application and constructors application.

The syntax for variant constructor application is C (x_1, ..., x_n). Thus the expression

s |> Ticker

is parsed as

(|>) (s) (Ticker)

where Ticker is missing its argument.

One might wonder why OCaml does not unify function and constructor applications like Haskell. One partial explanation for this difference is that this is a consequence of allowing mutation.

Indeed, in presence of mutations (and thus with the (relaxed) value restriction), variant constructor applications do not have exactly the same semantic as function applications in OCaml.

For instance, if I define a wrapper type as

type 'a w = W of 'a
let int_minus = (~-)
let float_minus = (~-.)

The following example:

let ok =
  let x = W Fun.id in
  [x; W int_minus], [x; W float_minus]

is well-typed. However, if I replace the constructor application by the constructor function

let w x = W x

in this example

let error =
  let x = w Fun.id in
  [x; w int_minus], [x; w float_minus]

This triggers an error due to the value restriction:

Error: This expression has type (float -> float) w but an expression was expected of type (int -> int) w Type float is not compatible with type int

The main difference is that a generic function might use internally mutable references. Thus the type of

let x = w Fun.id

is ('_weak1 -> '_weak1) w where _weak1 is a weakly polymorphic type variables (aka a placeholder for a concrete type). This placeholder type is unified with int when I write [x; w int_minus]. Then, trying later to build [x; w float_minus] yields to an error because I am trying to use x with type (float->float) w.

Contrarily, a variant constructor application cannot do any computation, thus it is not subject to this limitation and the type of

let x = W Fun.id

is the expected fully polymorphic type ('a -> 'a) w. Consequently, I can reuse x with two different types for 'a in

[x; W int_minus], [x; W float_minus]

(See https://ocaml.org/manual/polymorphism.html#s:weak-polymorphism for a more thorough explanation).

octachron
  • 17,178
  • 2
  • 16
  • 23
  • Thanks very much for the thorough answer. I think it'll take me a bit to understand everything you presented here; I am interested in understanding why constructors don't work well as functions in ocaml. – Jake Aug 17 '21 at 17:14
  • 1
    In some sense, the issue is that constructors work better than functions because they are guaranteed to be pure (aka effect-free). – octachron Aug 17 '21 at 17:28
0

Ticker is a constructor-function which is typically used for defining a way to create values of certain type. Take a tour of Variants in OCaml.

If we look at the definition of Ticker

# type ticker = Ticker of string;;
type ticker = Ticker of string

Essentially, we are defining a Type called ticker whose values can be created using Ticker the constructor-function on the right which takes one value of type string. The constructor function itself is an expression whose type will be the type for which we just defined the variant.

However, if we look at the type of the pipelining operator(|>) ... it is a function type.

(|>);;
- : 'a -> ('a -> 'b) -> 'b = <fun>

and which expects a function type as it's second argument.

However with the case where it is breaking, the passed along type actually is a type ticker, and which is not a function type, hence a type-mismatch ... And shouted out load by the compiler, which we can see in the error produced.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Nalin Ranjan
  • 1,728
  • 2
  • 9
  • 10