Curious about needs for private record fields

What are people’s thoughts on declaring individual record fields as read-only or “no-read-no-write” in addition to the default read-write?

type a = private {
  id : string;
    (* read-only *)

  mutable acc : string list;
    (* read-only, mutable *)

  public mutable name : string;
    (* read-write once initialized *)
}

type b = private {
  id : string;

  hidden data : string;
    (* field cannot be accessed in any way *)
}

I’ve been enjoying the private keyword in front of records, variants or simple types since they exist. However, I sometimes feel the need for more granularity when using record types. The examples above illustrate two imaginary features:

  1. public fields which are read-write instead of read-only. This applies only to mutable fields.
  2. hidden fields which are not even readable instead of being read-only.

Note that I’m not designing a new language nor really requesting these features to be added to OCaml. I just feel they would be useful to me. Since they’re reminiscent of some features of object-oriented languages, including OCaml, I wonder if other people have had similar requirements or ideas.

1 Like

Each time I exposed a record type in an API I ended up regretting it.

The kind of access control you perform is better done via abstraction and accessors at the module system level in my opinion; especially since it’s more robust to refactoring.

2 Likes

I find that exposing trivial record fields lightens up module interfaces. Unfortunately, typically not all fields are considered “trivial” and I end up either making the whole record type abstract or exposing fields I shouldn’t.

Examples of trivial fields include elements of a form of some kind (id, name, description, …) and obvious product types (x, y, z).

Another benefit of getting/setting fields directly is an assurance of performance via transparency, e.g. no hidden hook or other extra code can run when doing p.x <- 3.. This is very relevant for some applications and not at all for others.

If you want to expose some fields but not others, you can declare abstract types for the private fields. Inside the module this type is just an alias to the actual type, but outside, it makes the field totally unusable.

E.g

type generative_id (* can't do anything *)

type ast = {
   view: ast_view;
   ty : type_;
   id: generative_id;  (* effectively private *)
}
and ast_view = Add of ast * ast | …
and type_ = …

edit: an additional benefit is that you can pattern-match on such records.

2 Likes

Did you mean the following?

type ast = private {
   view: ast_view;
   ty : type_;
   id: generative_id;
}

Declaring the record as private prevents the creation of other records with the same ID. However we can still access the id field. Maybe the user would use it as a key in a hash table or something - but if it’s an implementation detail that will change and we want to prevent them from referencing the field or at least reading from it. That’s the motivation for declaring a field as “hidden”.