Too impatient outputting optional ocaml function argument
Consider this code:
let myFun
?(f: ('a -> int) = (fun x -> x))
(x: 'a)
: int =
f x
It looks like I can't call any other argument except int
. When I try with this code:
let usage = myFun ~f:String.length "abcdef"
Ocaml emits this error message:
Error: This expression has type string -> int
but an expression was expected of type int -> int
Type string is not compatible with type int
It looks like the output will think 'a = int
because of the default argument. Is this a limitation of the language or is there a way how to write this so that it compiles?
source to share
This issue is a limitation due to the way the optionals argument is implemented.
Essentially, a function with an additional argument is expanded during the type-checking phase using the standard option type. For example your function:
let f ?(conv=(fun x -> x)) x = f x
becomes more or less
let f opt_conv =
let conv =
match opt_conv with
| None -> fun x -> x
| Some f -> f in
fun x -> conv x
Hence, since it opt_conv
has a type 'a->'a
in the branch None
, f must have for the type ?conv:('a->'a) -> 'a -> 'a
.
Looking at the extended function, the problem arises because the branch Some
and None
must be of a different type in order to get the desired functionality.
For quiet puzzles that have different types in different pattern matching branches, this is a sign that GADTs can bring a potential solution: you can define an extended option type as
type ('default,'generic) optional =
| Default: ('default,'default) optional
| Custom: 'a -> ('default,'a) optional
then you can rewrite your function as
let f: type a. (int -> int, a -> int) optional -> a -> int =
fun conv x ->
match conv with
| Default -> x
| Custom f -> f x
which leads to the expected behavior:
f Default "hi"
gives a type error whereas it f (Custom int_of_string) "2"
returns 2.
However, without the optional syntactic syntax mechanism, this is not very useful.
No, it's perfectly possible to extend OCaml to use the optional
GADT-laded type. However, this can easily lead to dire type errors, and the corresponding increase in complexity doesn't make for a very attractive extension.
source to share
The notation ?(f: ('a -> int)
does not mean that the parameter f
has a type 'a -> int
, it is a type constraint that tells the type inference algorithm what f
should be a function that returns a type value int
. 'a
here simply means, "I don't care, do it yourself." It doesn't generalize your type variable. Thus, given your constraint, the compiler reports that (1) the type f
is equal int -> int
(because it is the only input that satisfies your default), (2) the type is x
also int
because you constrained this yourself (it will be inferred for of the same type due to type f
.
Also, the type system is right here, forbidding anything other than int
as a parameter x
. Consider the following example:
myFun "hello"
According to your desire, it should be valid, but what should OCaml do in this case?
source to share
When you write
let myFun
?(f: ('a -> int) = (fun x -> x))
(x: 'a)
: int =
f x
You are asserting a generic type, but that is a mistake.
As you can see, when you interpret the function:
let myFun ?(f: ('a -> int) = (fun x -> x)) x = f x;;
val myFun : ?f:(int -> int) -> int -> int = <fun>`
Focus on this: f: ('a -> int) = (fun x -> x)
Here f
, the default is a take x
and return function, x
which means that the argument must be the same type as the return value. You wrote that it f
has a type 'a -> int
, then the return type int
, so the type of the parameter is also int
(and not 'a
).
You can just write: f: ('a -> 'a) = (fun x -> x)
and everything will work fine.
let myFun ?(f: ('a -> 'a) = (fun x -> x)) x = f x;;
val myFun : ?f:('a -> 'a) -> 'a -> 'a = <fun>
Update
If you want your function to always return int
, you need to change the default function. For example, you can write:
let myFun ?(f: ('a -> int) = (fun _ -> 0)) x = f x;;
val myFun : ?f:('a -> int) -> 'a -> 'a = <fun>
source to share