Reputation: 41
Thinking of a way to define a simple lens on an object in Reason.
I try to use extensible objects (with ..
prepended to field list) with the following code:
type hasName('a, 't) = {.. name: 't} as 'a;
type lens('s, 'v) = {
get: 's => 'v,
set: ('s, 'v) => 's,
};
let nameLens: lens(hasName('a, 't), 't) = {
get: s => s.name,
set: (s, v) => {...s, name: v},
}
I get "The record field name can't be found." error, though the type of hasName
should definitely have one... What am I doing wrong here?
Disclaimer: I am really new to Reason/OCaml, so I may miss some obvious things.
Upvotes: 4
Views: 136
Reputation: 29116
The error message isn't great (to put it mildly), but if you read carefully it does point to the problem. It says "The record field name can't be found.", which isn't so odd considering what you have is an object, not a record. This is an error because object members are accessed with #
, not .
(see the Reason docs on objects).
As I understand it, the reason for this is that OCaml doesn't support ad hoc polymorphism (ie. function or operator overloading) and so in order for the type inference to work well it therefore needs to distinguish operations on different types syntactically. I also suspect the reason it doesn't just say "dude, this is an object, not a record" is that the type system has no concept of any-record-type, but requires a specific record type. So it tries to first find the field in order to then identify the specific record type associated with it. If it can't find the field it isn't considered a type error because there is no proper type to conflict with.
In any case, the getter is solved easily enough by using #
instead of .
. The setter is more problematic, however. Here too you're using record update syntax and will get a similar error, but unfortunately there is no direct equivalent for objects.
One part of what makes objects object-oriented is that their implementation is hidden. You can't copy an object from the outside if you don't know what's inside. And while you can create a different object conforming to the same object type, you can only do so if you know the concrete type. Which in this case you don't.
So the way to update an object immutably is to do it from inside the object, in a setter method, where you know everything you need to know. The syntax for immutably updating the current object is {<name: newName>}
, which is only valid within a method definition. And then of course we also need to add this setter method to the hasName
type.
Putting this all together we end up with this:
type hasName('a, 't) =
{
..
name: 't,
setName: 't => 'a,
} as 'a;
type lens('s, 'v) = {
get: 's => 'v,
set: ('s, 'v) => 's,
};
let nameLens: lens(hasName('a, 't), 't) = {
get: s => s#name,
set: (s, v) => s#setName(v),
};
let obj = {
val name =
"myName";
pub name =
name;
pub setName = newName =>
{<name: newName>}
};
nameLens.set(obj, "newName")
|> nameLens.get
|> print_endline
Upvotes: 5