F# has Weird OO

A little while back I was learning about F#. For the most part F# is a cool language. It’s based on ML and is an impure functional language. Here is how you can do some things with F#:

Define a function:

foo a b = a + b

Call that function:

let x = foo 1 2

There is a lot more, like currying, powerful type inference, etc.

But I was learning F# because at work we were integrating with a .NET SDK and I am not super interested in writing C#. I did that in school and briefly during an internship and while you can certainly do good work, I’m just not super interested in writing C# if I can avoid it.

I purchased the early edition of Book of F# and for the most part I was really happy with it. I haven’t re-read it since they released another version with a few more chapters, but even the edition I read was very good. If you are interested in coding F# I highly recommend it.

The problem came when I started doing the object oriented code. In Perl you might do some OO code like this:

sub foo {
   my $friend = shift;
   die "that's not a valid friend!" unless $friend->isa('Human');
   ...
}

The above code simply dies if $friend is not an object based on the Human class. This will not die if you pass in an object based on a subclass of Human. Now let’s take it a tiny step further:

sub foo {
   my $friend = shift;
   die "that's not a valid friend!" unless $friend->isa('Human');
   print $friend->as_string;
}

This will print out whatever was returned from the as_string method defined on the class that $friend was defined as. So if $friend is a Human, we get the default as_string, but if it’s a subclass and as_string was defined in the subclass, it calls it on the subclass.

To be perfectly clear:

package Human;
use Moo;
has name => ( is => 'ro' );
sub title { 'human: ' . shift->name }

package CEO;
use Moo;
extends 'Human';
sub title { 'CEO: ' . shift->name }

package main;
use 5.14.0;
sub print_title {
   my $human = shift;
   die "that's not a valid human!" unless $human->isa('Human');
   say $human->title;
}

print_title(Human->new(name => 'frew'));
print_title(CEO->new(name => 'frew'));

should print

human: frew
CEO: frew

Now maybe perl is weird here, but as far as I know this makes perfect sense. Child isa Human (Liskov Substitution Principle) and calling the most specific method makes sense since otherwise how does polymorphism work at all?

Well, in F# OO is totally not like this.

Here is the same program in F#:

type Human(nombre: string) =
   member x.Name = nombre
   member x.Title() = sprintf "human: %s" nombre

type CEO(name) =
   inherit Human(name)
   member x.Title() = sprintf "CEO: %s" name

let print_title (p : Human) = printfn "%s" (p.Title ())

print_title (Human("frew"))
print_title (CEO("frew"))

This prints

human: frew
human: frew

The reason is that the p : Human type constraint basically does a cast. If you wanted to run the actual implemented method you can’t actually use OO at all. I’m really not sure why this is; I suspect it’s a limitation of the .NET platform because a coworker tells me that in C# the same thing happens.

UPDATE (2014-03-19): Some nice people on twitter informed me that I was missing something. Here is code that does what I actually want:

type Human(nombre: string) =
   member x.Name = nombre
   abstract member Title: unit -> string
   default x.Title() = sprintf "human: %s" nombre

type CEO(name) =
   inherit Human(name)
   override x.Title() = sprintf "CEO: %s" name

let print_title (p : Human) = printfn "%s" (p.Title ())

print_title (Human("frew"))
print_title (CEO("frew"))

The difference, if it is unclear, is that the base method is defined as abstract with a default implementation and the subclass is defined with override.

On the other hand, here is a cool(ish) thing.

F# lets you define interfaces on objects, which is like what Moo(se) users might call a crippled role. An interface basically requires a certain set of methods following a certain interface to be defined. The interesting thing is that a user can define methods of The same name under different interfaces. So for example, check this out:

type IHasName =
   abstract member Name : unit -> string

type IHasNickName =
   abstract member Name : unit -> string

type Human(nombre) =
   interface IHasName with
      member x.Name() = nombre
   interface IHasNickName with
      member x.Name() = nombre + "y"

printfn "name %s" ((Human("frew") :> IHasName).Name ())
printfn "nickname %s" ((Human("frew") :> IHasNickName).Name ())

The above prints

name frew
nickname frewy

So it gets the string from the huamn that implements IHasName and then calls the Name method on it, and then it gets the string from the human that implements the IHasNickName interface and calls the Name method on it. A little weird, but I can see when that would be nice. The Trait based solution to this (renaming the method) is pretty hacky, this would always work, though certainly ends up a bit syntactically clunky.

Posted Mon, Mar 17, 2014

If you're interested in being notified when new posts are published, you can subscribe here; you'll get an email once a week at the most.