Reputation: 8854
Is there a way to make optional argument f
flexible enough to have type 'a -> 'b
, yet still make it default to identity
, given that identity
has type 'a -> 'a
?
An earlier question begins by stating my question, exactly:
I want to define a function that accepts an optional argument which is a function ('a -> 'b). The default value should be the identity, which is actually ('a -> 'a), but i see no reason why it should not be compatible with the more general ('a -> 'b).
However, the question then includes an example that exemplifies a narrower problem. The answers to that question respond to the narrower problem. Here is a simplistic illustration of the general issue:
# let g1 ~f x = f x;;
val g1 : f:('a -> 'b) -> 'a -> 'b = <fun>
OK, that's the type I want. But I'd like to f
to default to an identity
function. That should be possible, since identity
has type 'a -> 'b
where 'b
is 'a
. But it doesn't work:
# let g2 ?(f=identity) x = f x;;
val g2 : ?f:('a -> 'a) -> 'a -> 'a = <fun>
Adding a type specification on identity
doesn't help:
# let g3 ?(f=(identity:'a -> 'b)) x = f x;;
val g3 : ?f:('b -> 'b) -> 'b -> 'b = <fun>
EDIT: After I posted this question, I discovered this question, which really is a very close duplicate of my question. So mark my question as a duplicate if you want. However, the answers to that question imply that there is no good use for what I want to do, and that's not true. Here are details:
The actual use case is a function that selects some elements from a list. The optional f
argument allows one to extract a bit of data from each element and use that data to decide whether to include that particular list element in the result. When elements are selected by the whole element, f
should be identity
. The actual function I'm trying to define is for lazy lists. Here's a simplified version for lists, with L
as an alias for List
:
let select ?(accessor=identity) keys vals =
let rec sel ks vs =
if ks = [] || vs = [] then []
else let k, v = L.hd ks, L.hd vs in
let v_key = accessor v in
if k = v_key then v::(sel (L.tl ks) (L.tl vs))
else if k > v_key then sel ks (L.tl vs) (* let vs catch up *)
else sel (L.tl ks) vs (* let ks catch up *)
in sel keys vals
Simple use:
# let vs = Batteries.List.range 1 `To 100;;
# let ks = [4; 10];;
# select ks vs;;
- : int list = [4; 10]
The more general use is when elements of ks
are, say, records with a key field that's an integer. Then the accessor
function would map from the record type to int
.
(Yes, I know that the use of hd
and tl
is a bit unusual. It translates into the lazy list context better.)
Upvotes: 6
Views: 475
Reputation: 4441
I found your question while I was asking myself the exact same question and I found a solution that is a bit dirty but yet working:
You can't do it on the function level but on the result one:
external identity : 'a -> 'b = "%identity"
let to_array ?(f = identity) s = Array.init (String.length s) (fun i -> f s.[i])
And then when you use it without any argument:
let c : char array = to_array "abc";;
val c : char array = [|'a'; 'b'; 'c'|]
Obviously, this can lead to some really bad behaviour:
# let c : string array = to_array "abc";;
Process OCaml segmentation fault
So I wouldn't advise doing it but it's not impossible
Upvotes: 0
Reputation: 66803
I think the way to look at this is to imagine what type your function would have. You seem to be asking for this type:
?f:('a -> 'b) -> 'a -> 'b
However, this doesn't capture the idea that when you leave out the optional parameter, the type reverts to 'a -> 'a
.
Instead, what this type says is that if you leave out the optional parameter, you have a function of type 'a -> 'b
. But there is no well-formed function of type 'a -> 'b
. This can only be the type of a function that never returns (or similar).
My take on this is that the OCaml type system can't represent the function you want, even though it does make sense.
Upvotes: 3
Reputation: 9377
OCaml simply does not support this. You cannot write a function with a type that is refined depending on whether an optional argument has been passed.
Unlike some the answers in the linked question, I agree that this is a reasonable thing to want to do. Indeed, typing things like Common Lisp's sequence functions (with :key
and :test
) seems to require something like this. However, OCaml is not a language in which it can be done.
The most reasonable approach is probably to write two functions, one of which takes an accessor as non-optional argument, and the other which supplies identity
as that argument:
let g f x = f x
let g_default x = g identity x
This is only a little clumsy, and you do not need to implement the logic in g
twice. However, applying this approach to combinations of more than one optional argument will become ugly.
Upvotes: 4