The following code:
class foo =
object
method f : 'a. 'a -> string = fun _ -> "f"
end
let bar : 'a. foo list -> 'a -> string list =
fun foos x -> List.map (fun ff -> ff#f x) foos
yields the error message
This expression has type foo list but an expression was expected of type
(< f : 'a -> 'c; .. > as 'b) list
Type foo = < f : 'a0. 'a0 -> string > is not compatible with type
< f : 'a -> 'c; .. > as 'b
The method f has type 'a. 'a -> string, but the expected method type was
'a -> 'b
The universal variable 'a would escape its scope
I don’t understand what the problem is. If I have a single foo
, I can apply its method f
to an argument of any type whatsoever, including the type 'a
in bar
. So why can’t I apply each element of a list of foo
s to an argument of that type?
I won’t claim to understand exactly what is going on but it’s a problem of the direction that type inference flows. If you just indicate that ff
is of type foo
it compiles fine.
class foo =
object
method f : 'a. 'a -> string = fun _ -> "f"
end
let bar : 'a. foo list -> 'a -> string list =
fun foos x -> List.map (fun (ff : foo) -> ff#f x) foos
Ok, that’s useful, thanks. The workaround I had found before was to define the mapped function separately, with a declared type, rather than inline.
Ah, and now with that hint I think I may also be able to guess why the first version doesn’t work. When the compiler sees a function that calls methods on some argument, it gives that function the most general possible type for the usages of those methods. So when it sees fun ff -> ff#f x
, where x
has type 'a
, it infers that the method f
must have type 'a -> 'c
for some type 'c
, and that therefore ff
must be an object that has a method of this type, < f : 'a -> 'c; .. >
. Thus the anonymous function has type < f : 'a -> 'c; .. > -> 'c
. Then it infers from the known type of List.map
that the second argument must have type < f : 'a -> 'c; .. > list
.
The type variable 'c
is existential and could get instantiated by anything, and the row variable ..
could likewise get instantiated by any extra methods, so for instance we could supply here something of type < f : 'a -> string; g : int > list
. But while the type < f : 'a -> 'c; .. >
is “more general” than that of foo
in an intuitive sense, since the method f
of foo
is required to be polymorphic rather than applying only to the type 'a
in bar
, this extra generality is not of the sort that the compiler knows how to deal with, so it complains. In other words, < f : 'a. 'a -> string >
is not syntactically a subtype of < f : 'a -> string >
, although semantically it could be.
(It probably would have been clearer to use different names for the essentially unrelated types 'a
in foo
and in bar
; sorry about that.)
2 Likes