Hiding virtual members is clearly problematic, since a virtual member must be defined in order to complete a classes definition. For example you could write:
module M : sig
class virtual c : object method foo : in end
end = struct
class virtual c : object (self) virtual method bar : int method foo = self#bar end
end
class d = object inherit M.c end
let _ = (new d)#foo
which would clearly error due to a missing bar
method. If you somehow kept track of the existence of the missing methods, then would just have a class that could not being inherited from – at which point you should just expose the class as an object type instead:
module M : sig
type c = private < foo : int >
end = struct
class virtual c : object (self) virtual method bar : int method foo = self#bar end
end
For public methods, the main issue is the one outlined by @schrodibear. That a class definition actually consists of three parts: a class definition, a class type definition and a type definition. Type definitions can be used in contravariant positions – which means it is not sound to subtype them. If you could hide the public methods then you could write:
module M : sig
class c : object end
val f : c -> int
end = struct
class c = object method foo = 3 end
let f c = c#foo
end
let _ = M.f (object end)
which again would error due to a missing foo
method.
The code is fundamentally the same as:
module M : sig
type c = < >
val f : c -> int
end = struct
type c = < foo : int >
let f c = c#foo
end
let _ = M.f (object end)
which perhaps makes things clearer, and also hints towards an approach that would work, because what you can write is:
module M : sig
type c = private < >
val f : c -> int
end = struct
type c = < foo : int >
let f c = c#foo
end
which hides the existence of the foo
method, by declaring the c
type to be a subtype of < >
, rather than declaring it to be equal to < >
as we did in the previous example. So what OCaml could support, but doesn’t, would be code like:
module M : sig
class c : private object method bar : int end
val f : c -> int
end = struct
class c = object method foo = 3 method bar = 4 end
let f c = c#foo
end
This would declare that M.c
was a subtype of object method bar : int end
. Compared to just exposing type c = private < bar : int >
this version would additionally allow you to inherit from c
to override its exposed methods:
module M : sig
class c : private object method bar : int end
val f : c -> int
end = struct
class c = object (self) method foo = self#bar method bar = 4 end
let f c = c#foo
end
class d = object
inherit c
method bar = 5
end
but you would not be able to extend it with new methods, since you would need to avoid accidentally overriding the hidden methods:
module M : sig
class c : private object method bar : int end
val f : c -> int
end = struct
class c = object (self) method foo = self#bar method bar = 4 end
let f c = c#foo
end
class d = object
inherit c
method foo = "five"
end
which would leave the object type of d
with two foo
methods with incompatible types.