Very basic question here. I wasn’t sure how to title the post because I’m not sure what the name is for the syntax I’m looking for.
I’d like to create a parameterized type that contains a reference to another of the same type, but regardless of its type parameter, to provide access to the non-parameterized fields.
Something like this:
type 'a person = {
name: string;
friends: '_ person list;
};;
or
type 'a person = {
name: string;
friends: 'b person list;
};;
neither of which compile.
An example of use is a function determining if one person is another person’s friend, where the type parameter is irrelevant.
let is_friend person friend =
person.friends
|> List.find ~f:(fun p -> String.equal p.name friend.name)
|> Option.is_some
I know functions are defined that ignore the type parameter all the time, like List.length. Is it possible to define a type’s field like this? How is it done?
Yes, you can use a simple GADT to introduce an “existential type” to hide that type parameter. It can be written like this:
type 'a person = {
name: string;
friends: any_person list;
}
and any_person = Any : _ person -> any_person
let is_friend person friend =
person.friends
|> List.find ~f:(fun (Any p) -> String.equal p.name friend.name)
type 'a person = {
name: string;
friends: vertex_view list;
other:'a;
}
and vertex_view = {
name: unit -> string;
friends: unit -> vertex_view list
}
let view x: vertex_view = {
name= (fun () -> x.name);
friends = (fun () -> x.friends)
}
let friends l = List.map view l
let a = { name = "A"; friends = []; other = "" }
let b = { name="B"; friends = friends [a]; other = 1 }
let is_friend person friend_name =
List.exists
(fun (f':vertex_view) -> friend_name = f'.name ())
person.friends
Thank you. Is your solution missing anything? When I paste it into emacs merlin complains that the label name is defined in both person and vertex_view.
Thanks very much! Both this and the solution without the GADT are illuminating, but this one seems to make the intention more explicit - it directly says it’s creating a type that hides the type parameter, which to me is clearer.
Can I ask what the purpose of the “wrapper function” is here? If I remove it like below everything seems to still work:
...
and vertex_view = {
name: string;
friends: vertex_view list
}
let view x: vertex_view = {
name = x.name;
friends = x.friends
}
Is it just that in a more “real-world” example you would probably want something other than a simple “same value” or is there some subtlety I’m missing?
This is a ignorable warning, which is disabled by default by the compiler since OCaml 4.10.0 .
The root issue is the immutable friends list in the definition of person. Such definition requires all members of a cycle to be simultaneously defined in a mutually recursive definition:
let rec a: _ person = {
name = "A";
friends = [ { friends = (fun () -> b.friends); name = (fun () -> b.name) }];
other = ()
}
and b = {
name = "B";
friends = [
{ friends = (fun () -> a.friends); name = (fun () -> a.name) }
];
other = 1
}
and this works (barely) because the access to b in the friends field of a are guarded under (fun () -> ...).