IMO, naming a variable of a type after the type is no problem and can be very nice for readability in cases like the one you’ve shown. Personally, I do not find person : person awkward at all, tho it may be redundant for such a small function, and I’d likely write that as p : person.
In general, I don’t see any need for (imo, unsightly) _t suffixes, because whether or not something is a type is completely unambiguous based on the syntax.
There is however a related convention in OCaml around use of modules, which I like very much:
If you define a (sub) domain with a principle type and a set of operators that act over values of that type, it is common (and expressive and flexible) to gather the type and the operations within a module like so:
module Person = struct
type t =
{ age : int
; name : string
}
let is_adult : t -> bool = ...
let name : t -> string = ...
(* etc. *)
end
(* example usage like *)
let octavia : Person.t = Person.{ name = "Octavia"; age = 58 }
let age = Person.age octavia
let name = Person.name octavia
t is the conventional name for principle types that get used in this way.
Just to make sure we’re on the same page, specifying the types like this is unnecessary the vast majority of the time thanks to type inference.
Save for the unnecessary type annotations, your example doesn’t bother me, so I guess it’s a matter of taste. However, distinguishing type names from value names is something I’ve hardly ever seen in any OCaml code, or in any functional code for that matter.
Also, it seems to me that in practice, value names tend to be more specific nouns than type names, since types represent classes of values. To be more concrete, a float called delta is common, whereas a float called float is a bit weird, except in specific contexts. So I don’t think the instance that bothers you arises a lot in practice.
Another advantage of naming the type t inside of a record Person is that the field names are qualified by the module and thus you can avoid conflict between same-named field of different types.
type student_t = { age: int }
let grade_level s = s.age - 5
type worker_t = { age: int }
(* even though we named the value student_a it has type worker_t because worker_t's definition for the age field shadows, or hides, the field from student_t *)
let student_a = { age = 14 }
(* so this code doesn't type check because student_a does not match the grade_level argument type *)
Printf.printf "grade level for student_a is %d\n" (grade_level student_a)
By defining types within modules, it is easy to specify which type of value we are creating:
module Student = struct
type t = { age: int }
let grade_level s = s.age - 5
end
module Worker = struct
type t = { age: int }
end
(* The Student. notation within the record specifies which module will be searched for the field names, ultimately determining the type of the value that is built. This results in student_a having type Student.t *)
let student_a = { Student. age = 14 }
(* and now this code will type check because student_a type matches the function argument type *)
Printf.printf "grade level for student_a is %d\n" (Student.grade_level student_a)
Yes, that’s a common pattern. Conventionally, OCaml code names those functions “make.” Usually each t type comes with a make function.
module Student = struct
type t = { age: int }
let make age = {age}
end
module Worker = struct
type t = { age: int }
let make age = {age}
end
For multiple types in the same scope, you could use make_student or make_worker, but it’s generally good to organize types into modules so the module names work as namespaces.
There is an older (2015) SO question about using “make” vs “create” naming for this. The consensus seemed to be “create” there. Do you think it is changed over the years to “make”, or is it is just personal preference?
This is probably personal preference. I got accustomed to using make when I learned OCaml so my code would be consistent with the way the stdlib uses it. I have no strong opinion about either word, though.
It’s worth mentioning that this syntax works because it locally opens Student within the expression. This is in contrast to the syntax where the module name comes right before the first field name. A disadvantage (or advantage, depending on your perspective) of the local open syntax is that you might accidentally shadow variables in the field value expression. In the best case, the shadowed name will have a different type and you’ll get a compile error, but in the worst case, you won’t realize, and the value will be silently wrong, this producing a bug. Then again, maybe you wanted to shadow the value…