One reason that F# interests me is its nominal object system. Although I am fond of OCaml's structural object system, I have been curious about nominal object systems, too. Unfortunately I have never exploited the full power of the object systems, like inheritance or overriding; now I will enjoy it through F#!
Having a nominal object system, type inference shall need some amount of type annotation. Indeed,
> let f x = x.callMe ();;does not type check; but I need to assist the inference engine as in
> type c = class
new () = {}
self.callMe () = 3
end;;
> let f (x:c) = x.callMe ();;
This is a difference from OCaml,
where the object system is structural, and
where the annotation on f's parameter is unneccessary
but the type inference can take care of it.
By the way, how do they infer types?
(ref.)
Like OCaml, subtyping is explicit (if I understand correctly). Yet, like OCaml, polymorphism is available. So
> type A = class
new () = {}
member self.f x = x
end;;
> type B = class
inherit A
new () = {}
end;;
> let a = new A ();;
> let b = new B ();;
I need explicit coercion if I fixed type:
> let callA (x:A) = x;; > let _ = callA a;; > let _ = callA (b :> A);;Yet I can define polymorphic callA, which does not require coercion:
> let callpolyA (x:#A) = x;; > let _ = callpolyA a;; > let _ = callpolyA b;;Anyway I like explicit subtyping as a programming practice.
> let f x y = (x, y);; > let g = f 3;; > type t = A;; > let x = g A;;Since F# supports neither functors nor local modules, strict conformity to the scope is not crucial perhaps.
> type point = class
val mutable x : int
new () = {x = 0}
member self.get_x = self.x
member self.move d = self.x <- self.x + d
end;;
type point = class
val mutable x: int
end
with
new : unit -> point
member get_x : int
member move : d:int -> unit
end
Note the separation between field declarations and their initialization;
I like this programming style.
> let p = new point ();; val p : point > p.get_x;; val it : int = 0 > p.move 3;; val it : unit = null > p.get_x;; val it : int = 3 > p.x;; val it : int = 3A big difference between OCaml and F#: in OCaml an instance variable is not visible outside the object, whereas in F# a field is visible outside the object.
> let x0 = ref 0;;
val x0 : int ref
> type point2 = class
val mutable x : int
new () = incr x0; {x = !x0}
member self.get_x = self.x
member self.move d = self.x <- self.x + d
end;;
Above I see another interesting design choice.
Side effecting expressions are syntactically separated from initialization
expressions. (ref) For now I do not know the impact of this design choice;
but it looks interesting.
> (new point2 ()).get_x val it : int = 1 > (new point2 ()).get_x val it : int = 2Now I see a divergence from OCaml; there is no faithful correspondence to the following class definition from OCaml manual:
#class point3 = fun x_init ->
object
val mutable x = x_init
method get_x = x
method move d = x <- x + d
end;;
In a structural object system, class definitions are, broadly speaking,
function definitions.
(OCaml generates from class definitions type abbreviations.
Although this is very useful for the practical use
in my opinion, abbreviation generation is dispensable
from a theoretical point of view.) In a nominal object system however,
class definitions define both values and types simultaneously,
which seems to hinder the translation of the above class definition into F#.
(e.g., In a nominal system distinct class definitions necessarily generate distinct types,
which is not the case in a structural system.)
Here is a slightly different version:
#class point3 x_init =
object
val mutable x = x_init
method get_x = x
method move d = x <- x + d
end;;
A natural translation into F# may be
> type point3 = class
val mutable x:int
new (x_init) = {x = x_init}
member self.get_x = self.x
member self.move d = self.x <- self.x + d
end;;
> fun x_init -> new point3 (x_init);;
val it : int -> point3 = <...>
> >let p = new point3 7;;
val p : point3
Be careful fun x_init -> new point3 x_init is syntax error;
parentheses are mandatory.
A difference between structural and nominal objects also appears when translating the following OCaml class:
#class point4 x_init =
object
val mutable x = x_init
method get_x = x
method get_offset = x - x_init
method move d = x <- x + d
end;;
The following F# class may be a natural translation.
> type point4 = class
val x_init: int
val mutable x:int
new (init) = {x = init; x_init = init}
member self.get_x = self.x
member self.get_offset = self.x - self.x_init
member self.move d = self.x <- self.x + d
end;;
You see the difference. The parameter x_init is visible in the whole body of
the class definition point4 in OCaml,
which is not the case in F#, thus point4 in F# has
an extra field. (Both may be boiled down to the same closure. Note that the
x_init field of F# point4
is immutable.)
#class point5 x_base x_init=
object
val mutable x = x_init
method get_x = x
method get_offset = x - x_base
method move d = x <- x + d
end;;
#let point_0 = new point5 0
> type point5 = class
val x_base: int
val mutable x:int
new (base, init) = {x_base = base; x = init}
member self.get_x = self.x
member self.get_offset = self.x - self.x_base
member self.move d = self.x <- self.x + d
end;;
> let point_0 = fun init -> new point5 (0, init)
Are OCaml point_0 and
F# point_0 ultimately same?
> type adjusted_point = class
val mutable x: int
val origin: int
new (init) = let x_init = (init / 10) * 10 in { x = x_init; origin = x_init}
member self.get_x = self.x
member self.get_offset = self.x - self.origin
member self.move d = self.x <- self.x + d
end;;
I re-remembered a difference between structural and nominal objects.
In OCaml all classes of point, point2 and point3
are equivalent types, whereas in F# they are distinct.
So to make my encoding semantically closer to the original
(i.e., OCaml manual),
I should have first defined an interface and let the classes
implement the interface so that these classes can be coerced into the same
interface.
So there is no exact correspondence in F# to the following OCaml class definition.
#class adjusted_point x_init = point4 ((x_init / 10) * 10);;A close definition may be this:
> type adjusted_point = class
inherit point4
new (init) = let x_init = ((init / 10) * 10) in = { inherit point (x_init)}
end;;
Again two classes point4 and adjusted_point are
equivalent in OCaml, whereas they are distinct in F#.
(adjusted_point is coercible to
point4, but not vice versa.)
The following two functions may correspond exactly.
#let new_adjusted_point x_init = new point4 ((x_init / 10) * 10);;
> let new_adjusted_point x_init = new point4 ((x_init / 10) * 10);;
Below are the two things I have learned. (ref.)
> type point6 = class
val mutable x:int
new (init) = {x = init}
member self.get_x = self.x
abstract get_offset : int
member self.move d = self.x <- self.x + d
end;;
> let new_point6 init base =
{ new point6 (init) with
member self.get_offset = self.x - base end };;
val new_point6 : int -> int -> point6
> type c = class new () = {} end;;
> type d = interface end;;
> let x = { new c () interface d end};;
> let f_d (x:d) = x;;
> let _ = f_d x;; // this does not type check
To retrieve an interface of an immediate object, I must resort to dynamic
type test (ref.).
Is this due to the nominal object system? Or this is a design choice?
The reliance on the dynamic type test looks a limitation for me.
Anyway OCaml does not have such dynamic type test,
so I am not familiar with it.
> type mm = class
new () = {}
abstract min : int
abstract max : int
end;;
> let minmax x y =
if x < y then {new mm () with member self.min = x member self.max = y end}
else {new mm () with member self.min = y member self.max = x end};;
val minimax : int -> int -> mm
I could have relied on implicit constructors:
> type mm2 (min: int, max: int)= class
member self.min = min
member self.max = max
end;;
> let minmax2 x y = if x < y then new mm2 (x, y) else new mm2 (y, x);;
val minmax2 : int -> int -> mm2
However, as far as I know, implicit constructors are under design
or under implementation;
at least I am not able to find documentation describing its general treatment.
Usually I do not use constructs that I cannot understand,
hence I will not use implicit constructors for the moment.
> type printable_point = class
val mutable x: int
new (init) = { x = init }
member self.get_x = self.x
member self.move d = self.x <- self.x + d
member self.print = print_int self.get_x; print_newline ()
end;;
> let p = new printable_point 7;;
> p.print;;
7
val it : unit = null
If I understand correctly, F# does not have explicit self variables.
Indeed, I was rather surprised by this result of the type inference:
> type c = class member self.me = self end;;
type c = class
end
with
member me : c
end
> type printable_point2 = class
val mutable x:int
new (init) as this =
let origin = (init / 10) * 10 in { x = origin }
then print_string "new point at "; this.print; print_newline()
member self.get_x = self.x
member self.move d = self.x <- self.x + d
member self.print = print_int self.get_x
end;;
> let p = new printable_point2 17;;
val p : printable_point2
new point at 10
Object initialization appears different between OCaml and F#.
So let's examine it in more detail.
> let print i = print_int i; print_newline ();;
> type c1 = class
val x: int
new () as this = { x = 0 } then print this.x
end
> new c1 ();;
0
It is safe to dereference this within the trailing expression.
> type c2 = class
val x1: int
val x2: int
new () as this = { x1 = 0; x2 = this.x1 }
end;;
warning: .... < the possibility of a null pointer exception > ...
> new c2 ();;
System.NullReferenceException
> type c3 = class
val x: c3
new () as this = { x = this }
end;;
warning: .... < the possibility of a null pointer exception > ...
> new c3 ();;
System.NullReferenceException
> let discard x = 0;;
> type c4 = class
val x: int
new () as this = { x = discard this }
end;;
warning: .... < the possibility of a null pointer exception > ...
> new c4 ();;
System.NullReferenceException
Ok. this is evaluated (dereferenced?) even if I do not access its
fields. But a reference to this should be safe if it occurs
under abstraction:
> type c5 = class
val x: unit -> c5
new () as this = { x = fun () -> this }
end;;
error: binding null type in envBindTypeRef: .ctor@3
What does this error mean? Anyway c5 type checks with fsc.
1) In F# I have access to "base", i.e., the inherited object, during object construction.
> type c6 = class
val i1: int
new (init) = { i1 = init }
end;
> type c7 = class
inherit c6 as base
val i2: int
new (init) = { inherit c6(init); i2 = base.i1 * 2 }
end;;
> let x = new c7 3;;
> x.i1;;
val it : int = 3
> x.i2;;
val it : int = 6
There is no equivalent in OCaml side.
(Of course, you have encodings for a similar effect.)
2) Side effecting expressions before/after object construction interact with inheritance in different ways.
> type c8 = class
val i1: int
new (init) = let _ = print_string "A" in
{ i1 = (print_string "B"; init) }
then print_string "C"
end;;
> type c9 = class
inherit c8
val i2: int
new (init) = let _ = print_string "D" in
{ inherit c8 (init); i2 = (print_string "E"; init) }
then print_string "F"
end;;
DABCEF
#class c8 init =
let _ = print_string "A" in
object
val i1 = (print_string "B"; init)
initializer print_string "C"
end;;
#class c9 init =
let _ = print_string "D" in
object
inherit c8 init
val i2 = (print_string "E"; init)
initializer print_string "F"
end;;
DABECF
As a result, F#'s design lets me observe a null-pointer exception:
> type c10 = class
new () as this = {} then this.f ()
abstract f: unit -> unit
end;;
> type t = A of int;;
> type c11 = class
inherit c10
val data: t
new () = { data = A 0 }
override self.f () = match self.data with A i -> print_int i
end;;
System.NullReferenceException;;
I am a little unhappy that the compiler does not even warn me the
possibility. It's not fair of me to complain about it, considering F#'s
interoperability with .NET languages :-)
> type abstract_point = class
val mutable x: int
val x_init: int
new (init) = { x = init; x_init = init }
abstract get_x : int
member self.get_offset = self.get_x - self.x_init
abstract move: int -> unit
end;;
type abstract_point = class
abstract member get_x : int
abstract member move : int -> unit
val mutable x: int
val x_init: int
end
with
new : init:int -> abstract_point
member get_offset : int
end
> type point7 = class
inherit abstract_point
new (init) = { inherit abstract_point (init) }
override self.get_x = self.x
override self.move d = self.x <- self.x + d
end;;
type point7 = class
inherit abstract_point
end
with
new : init:int -> point7
end
One thing I feel unconformable about virtual classes of F# is that
there is no explicit keyword, like the OCaml's virtual keyword,
specifying that a class is virtual, hence is not instantiatable.
Although the compiler gives me a warning, the type of a class does
not necessarily indicate whether the class is virtual or not.
For instance:
> type point8 = class
inherit abstract_point
new (init) = { inherit abstract_point (init) }
override self.get_x = self.x
end;;
type point8 = class
inherit abstract_point
end
with
new : init:int -> point8
end
Of course, I cannot instantiate point8;
such attempt is statically rejected.
i.e., new point8 0 is a compile-time error.
As far as I understand, there is no correspondence in F# to virtual instance variables of OCaml.
> type restricted_point_type = class
abstract get_x: int
abstract bump: unit
end;;
> fun (x: restricted_point_type) -> x;;
val it : restricted_point_type -> restricted_point_type = <...>
I do not think there is a F# equivalent of the following OCaml class definition.
#class restricted_point' x = (restricted_point x : restricted_point_type);; class restricted_point' : int -> restricted_point_typeIndeed this mechanism looks particular to structural object systems, where class definitions do not generate nominal types. Note that this is not same as constraining (object) constructors, since the class restricted_point' is still inheritable.
#class restricted_point2' = object inherit restricted_point' 0 method move = "I will not move" end;;Is it meaningful to make a comparison between OCaml and F# on this behavior, considering that instance variables and concrete private methods can be hidden here? Remember that both of instance variables and concrete private methods are invisible to object clients, but are only visible to class clients.
> type colored_point = class
inherit point4
val c : string
new (x,c) = { inherit point4 (x); c = c }
member self.color = self.c
end;;
type colored_point = class
inherit point4
val c: string
end
with
new : x:int * c:string -> colored_point
member color : string
end
> let p' = new colored_point (5, "red");;
val p' : colored_point
> p'.get_x, p'.color;;
val it : int * string = (5, "red")
> type printable_point3 = class
val mutable x:int
new (init) as this =
let origin = (init / 10) * 10 in { x = origin }
then print_string "new point at "; this.print; print_newline()
member self.get_x = self.x
member self.move d = self.x <- self.x + d
abstract print : unit
default self.print = print_int self.get_x
end;;
> type printable_colored_point = class
inherit printable_point3 as super
val c:string
new (y, c) = { inherit printable_point3 (y); c = c }
member self.color = self.c
override self.print =
print_string "(";
super.print;
print_string ", ";
print_string (self.color);
print_string ")"
end;;
> let p' = new printable_colored_point (17, "red");;
new point at (10, )
Why...? Yes, I remembered the order in which trailing expressions are evaluated.
> p'.print;; (10, red)
> type ref = class
val mutable x:int
new (x_init) = { x = x_init }
member self.get = self.x
member self.set y = self.x <- y
end;;
> type 'a ref2 = class
val mutable x : 'a
new (x_init) = { x = x_init}
member self.get = self.x
member self.set y = self.x <- y
end;;
> let r = new (_ ref2) 1 in r.set 2; r.get;;
val it : int = 2
Below is a little experiment.
> type 'a ref_succ = class
val mutable x: 'a
new (x_init) = { x = x_init + 1 }
member self.get = self.x
member self.set y = self.x <- y
end;;
stdin(3,2): error: This code is not sufficiently generic.
The type variable ^a when ^c : (static member ( + ) : ^c * int -> ^a)
could not be generalized because it would escape its scope.
I do not well-understand what this error message means.
Yet let me try with another example.
> type succ = class
val mutable x:int
new (x) = { x = x }
member self.get = self.x
abstract set: int -> unit
default self.set y = self.x <- y
end;;
> type 'a ref_succ when 'a :> succ = class
val x: 'a
new (x) = { x = x }
member self.get = self.x.get
member self.set y = self.x.set y
end;;
> type succ_double = class
inherit succ
new (x) = { inherit succ (x) }
override self.set y = self.x <- y*2
end;;
> let x = new succ_double 3;;
> let y = new (_ ref_succ) (x);;
My conclusion is that, probably, F# does not infer constraint unlike OCaml.
> let _ = (y :> succ ref_succ);; errorAs written in the manual (see the 3rd entry of "The Coercion Relation" paragraph), F# generic types do not support co-variance or contra-variance.
Below type checks.
> type 'a ref_succ_double when 'a :> succ_double = class
inherit 'a ref_succ
new (x) = { inherit ('a ref_succ) (x) }
end;;
> let z = new (_ ref_succ_double) (x);;
> let z' = (z :> succ_double ref_succ);;
val z' : succ_double ref_succ
The type checker rejects untyped inheritance such as:
> type 'a ref_succ_bad = class
inherit 'a ref_succ
new (x) = { inherit ('a ref_succ) (x) }
end;;
error
Now I return to the OCaml manual.
#class ['a] circle (c : 'a) = object constraint 'a = #point val mutable center = c method center = center method set_center c = center <- c method move = center#move end;;
> type 'a circle when 'a :> point4 = class
val mutable c: 'a
new (c) = { c = c }
member self.center = self.c
member self.set_center c = self.c <- c
member self.move = self.c.move
end;;
type 'a circle when 'a :> point4 = class
val mutable c: 'a
end
with
new : c:'a -> 'a circle
member center : 'a
member move : (int -> unit)
member set_center : c:'a -> unit
end
OCaml uses row polymorphism, whereas F# uses subtyping polymorphism.
(I hope my understanding is correct.)
Besides compare the above F# circle with the following circle2:
> type 'a circle2 when 'a :> point = class
val mutable c: 'a
new (c) = { c = c }
member self.center = self.c
member self.set_center c = self.c <- c
member self.move x = self.c.move x
end;;
type 'a circle2 when 'a :> point4 = class
val mutable c: 'a
end
with
new : c:'a -> 'a circle2
member center : 'a
member move : x:int -> unit
member set_center : c:'a -> unit
end;;
circle2 looks closer to the OCaml circle
in respect of types.
> type 'a colored_circle when 'a :> colored_point = class
inherit 'a circle2
new (c) = { inherit (_ circle2) (c) }
member self.color = self.c.color
end;;
type 'a colored_circle when 'a :> colored_point =
class
inherit 'a circle2
end
with
new : c:'a -> 'a colored_circle
member color : string
end
> let colored_point_to_point cp = (cp : colored_point :> point4);; error: syntax error > let colored_point_to_point (cp : colored_point) = (cp :> point4);; val colored_point_to_point : colored_point -> point4Indeed there would be no reason that F# needs to support OCaml style double coercion, since the necessity comes from the interaction between subtyping and the full type inference.
> let p = new point4 3 and q = new colored_point (4, "blue");; val q : colored_point val p : point4 > let l = [p; (colored_point_to_point q)];; val l : point4 list > (p :> colored_point);; error: Type constraint mismatch.Unfortunately and fortunately, F# supports dynamic type tests.
> let q' = colored_point_to_point q;; (q' :> colored_point);; error: Type constraint mismatch. (q' :?> colored_point);; val it : colored_point = ...
> let to_point (cp: #point4) = (cp :> point4);; val to_point : #point4 -> point4 > let colored_point_to_point2 (cp : #colored_point) = (cp :> point4);; val colored_point_to_point2 : #colored_point -> point4
> type window = class
val mutable _top_widget : widget option
new () = { _top_widget = None }
member self.top_widget = self._top_widget
end
and widget = class
val _window: window
new (w) = { _window = w}
member self.window = self._window
end;;