Why `n:int -> int` is not compatible with `int -> int`?

add1 (of type n:int -> int, with labeled parameter) cannot be used as the first argument (of type 'a -> 'b, without labels) in List.map:

let add1 ~(n: int) : int = n + 1

let nums = [1; 2; 4; 8]

let result = List.map add1 nums (* Error: add1 has type n:int -> int
       but an expression was expected of type 'a -> 'b *)

What’s the rationale behind this restriction?

In OCaml, labels such as ~n are part of the type, so n:int -> int and int -> int are not compatible types. One could imagine systems where one could implicitly cast values from type n:int -> int to values of type int -> int but these systems are more complex and this is not done in OCaml.

Cheers,
Nicolas

3 Likes

Adding a label to a type doesn’t look like much effort to me. Can you briefly explain where the complexity lies?

This would be ambiguous. Let’s extend your example

let f ~n m = 1 + n  + m * m
let g xs = List.map f xs

then there is two way to interpret the g function, either

let g xs = List.map (fun x y -> f ~n:x y) xs

because obviously n is the first parameter of f; or

let g xs = List.map (fun x y -> f ~n:y x) xs

because obviously m is the first positional argument of f.

And this happens for a very simple application and type.

Rather than trying to create elaborated implicit conversion rules (which leads to many issues once such conversion rule can be chained), OCaml eschews any implicit conversion.

This creates some boilerplate from time to time.

However, it helps to make sure that both the caller and callee agree on the meaning of labels.
Labels often are used to make function more readable and easier to use, this erasing a label should be a conscious decision.

Also @unfode would you mind trying to not use screenshot to post code? Images are not accessible and makes the forum harder to read for blind users.

5 Likes

No problem.


So the main problem seems to be not working well with partial application. That makes sense to me.

But it’s really a pity not being able to use functions like map, fold_left, etc. with labeled functions as arguments.

Most of the time you would design functions such that the labelled parameters would not be the ones that would be injected by higher-order functions like map anyway, so having a label doesn’t really add any extra boilerplate. E.g.

let add ~x y = x + y
let nums = List.map (add ~x:1) [1; 2; 3]

In your example, x and y of add are “equal” in terms of importance. It doesn’t make sense to me that x has a label while y doesn’t. When you use add directly, wouldn’t it be weird to write let sum = add ~x:1 2?

It was just an example, the specific example here isn’t that important. I’m just trying to show a design pattern here.

EDIT: here’s another contrived example but perhaps it gets the point across:

let increase ~by x = x + by
let results = List.map (increase ~by:1) [1; 2; 3]
1 Like

In view of the “equality” it makes no sense to label them at all.

1 Like

I tried F# today, where parameters are labelled by default. Example:

let greet (name: string) : string = $"Hello {name} from F#!"

let greet_lambda: string -> string = fun name -> $"Hi, {name}!"

let names: string list = ["Alice"; "Bob"]

let greetings = List.map greet names
let greetings_lambda = List.map greet_lambda names

Notice the type of greet_lambda:

I like this better so far.