How to name a type, `person` or `person_t`?

Suppose I define a new type for persons. I can call the type person:

type person = {
  age: int;
}

But when a function has a parameter of type person, it feels awkward to call the parameter also person:

let is_adult (person: person) : bool = person.age >= 18

So I think person_t (_t to indicate it’s a type) may be a possible remedy:

type person_t = {
  age: int;
}
let is_adult (person: person_t) : bool = person.age >= 18

What do you think?

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.

9 Likes

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.

8 Likes

I’d even omit naming the argument for the above example, not only the type:

let is_adult { age } = age >= 18
1 Like

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.

Taking your example from Are OCaml records structurally typed or nominally typed? can show how the fields of a newly defined type shadow the previous.

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)
1 Like

I prefer to define “constructors” for my type, e.g.

let new_worker (age: int) : worker_t = {age = age;}

and use constructors to create records. Actually, I avoid creating a record by {age = age;} unless inside a constructor.

Do you think this is good practice?

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.

1 Like

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.

1 Like

There is also the syntax

Student.{ age = 16 }

Just pick a convention and stick with it. Personally I use make for immutable values and create for mutable values.

2 Likes

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…

6 Likes