module User = struct
type first_name = FirstName of string
type last_name = LastName of string
let with_first_name str = FirstName str
let with_last_name str = LastName str
let greet (FirstName fn) (LastName ln) = "Hello " ^ fn ^ " " ^ ln ^ "!"
end
let _ =
print_endline @@ User.greet (FirstName "John") (LastName "Doe");
let jane = User.with_first_name "Jane" in
let doe = User.with_last_name "Doe" in
print_endline @@ User.greet jane doe
;;
I think that is type inference at is best. Ocaml is able to figure out, because there is no other thing in scope, which variants you are referring to. I don’t have a deep understanding, but I know that types and values are on separate namespaces, so the fact that one needs to be scoped does not guarantee that the other needs it
I believe this is a case of “type-directed disambiguation”; basically the typechecker uses contextual information (in this case, the signature of User.greet to resolve constructor (and record label) names.
In the second case, let john = FirstName "..." in ... there isn’t enough “contextual information” to disambiguate FirstName.
Indeed, this is type-directed disambiguation at work. If enabled the warnings name-out-of-scope and disambiguated-name provides more information:
User.greet (FirstName "John")
Warning 40 [name-out-of-scope]: FirstName was selected from type User.first_name.
It is not visible in the current scope, and will not
be selected if the type becomes unknown.
Warning 42 [disambiguated-name]: this use of FirstName relies on type-directed disambiguation,
it will not compile with OCaml 4.00 or earlier.
Good to know. Is using this feature considered good practice? Or would you say turning on all warnings is probably a better option for a new comer to the language?
Having read a good part of the links provided by @nojb, I couldn’t make out if it was generally accepted as a good feature to use, or just something nice to have for quick prototyping.
I would say that it is a good feature to be used without restriction
The only downside I can think of is that it may sometimes complicate moving code around because you can take a piece of code that typechecks in a given context, but once you move it to some other place it no longer does due to insufficient contextual information. But I don’t think this is a big issue in practice.
I find it’s curious that this case surprisingly works… while others which seem, to me, less ambiguous don’t.
e.g. I have some code like:
let roots = List.map O.of_list pts in
fun () ->
let open O in
List.map (fun root ->
List.map (fun pt -> nearest root.tree pt) targets
) roots
in this code the type of roots is O.t list where O.t is a record type
but I can’t do root.tree without opening the O module, despite the type inference (at least the one in VS Code) seems to be fully aware that root is an O.t record.
I guess there is some technical reason, but naĂŻvely it seems like this should be inferable without the explicit module opening.
The type of root is not yet known when the typechecker encounters root.tree in your code.
The typechecker thus cannot use type information to find the tree field.
This is the reason why adding an annotation:
fun (root:O.t) -> ... root.tree ...
removes the need to open the O module.
But you are right that type-directed disambiguation is sensitive to the precise flow of type information inside the OCaml typechecker (aka it is not principal) which is one of the reason why the current implementation of type-directed disambiguation is not considered to be satisfying at the theoretical level.
But it’s interesting that without either the explicit open or the extra annotation, if I hover over the root var in VS Code it’s able to tell me it’s an O.t.
It’s not clear to me why the type of root would not be known if the type of roots is known - seems like the signature of List.map should ensure that it’s known too.
The type of root is not yet connected to the type of roots at the time the typechecker is looking at root.t because the type of the argument is checked first.
It this reliance on typechecking time that makes type-directed disambiguation not completely satisfying on the theoretical level.