Skip to content

Semantics of toplevel let-bindings in classes #13178

@lthls

Description

@lthls

I have a question that can be explained with this example (and some variations later):

module M1 = struct
  let () = print_endline "Before class"
  class c =
    let () = print_endline "Class init" in
    object end
  let () = print_endline "After class"
  let o1 = new c
  let o2 = new c
end

The manual currently specifies that the "Class init" line should be evaluated between the two other print statements, and nothing printed when evaluating o1 and o2 (see https://ocaml.org/manual/5.2/classes.html#sss:class-localdefs). The implementation mostly follows that.

However, the implementation is quite complex and in particular, adding a local open can trick it into believing that the let bindings are not toplevel anymore:

module M2 = struct
  let () = print_endline "Before class"
  class c =
    let open Unit in
    let () = print_endline "Class init" in
    object end
  let () = print_endline "After class"
  let o1 = new c (* Prints here *)
  let o2 = new c (* Prints here again *)
end

I will propose a PR that fixes this issue, but I'd like to know why we even bother with that corner case: making everything under the class definition evaluated at object creation time seems reasonable to me and easier to implement and specify.

In particular, in the following definitions M4 and M5 behave like M2 (printing pushed at object creation time) despite being mostly equivalent to M3 and M1 respectively (which both print at class creation time):

module M3 = struct
  class with_param p = object end
  let () = print_endline "Before class"
  class c =
    let () = print_endline "Class init" in
    with_param ()
  let () = print_endline "After class"
  let o1 = new c
  let o2 = new c
end

module M4 = struct
  class with_param p = object end
  let () = print_endline "Before class"
  class c =
    (let () = print_endline "Class init" in
     with_param)
      ()
  let () = print_endline "After class"
  let o1 = new c
  let o2 = new c
end

module M5 = struct
  let () = print_endline "Before class"
  class c =
    (let () = print_endline "Class init" in
     object end : object end)
  let () = print_endline "After class"
  let o1 = new c
  let o2 = new c
end

Finally, the part in the manual about class application (https://ocaml.org/manual/5.2/classes.html#sss:class-app) is either wrong or misleading. The arguments passed to the class constructor are always evaluated at object creation time, not immediately, as can be seen on the following examples (in both cases, "Class param" is printed when creating the objects):

module M3_app = struct
  class with_param p = object end
  let () = print_endline "Before class"
  class c =
    let () = print_endline "Class init" in
    with_param (print_endline "Class param")
  let () = print_endline "After class"
  let o1 = new c
  let o2 = new c
end

module M4_app = struct
  class with_param p = object end
  let () = print_endline "Before class"
  class c =
    (let () = print_endline "Class init" in
     with_param)
      (print_endline "Class param")
  let () = print_endline "After class"
  let o1 = new c
  let o2 = new c
end

Cc @garrigue, our main reference on objects and classes.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions