Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# and duck-typing

Let's say I defined in F# the following two types:

type Dog = { DogName:string; Age:int }
type Cat = { CatName:string; Age:int }

I was expecting the following method to work for both cats and dogs:

let isOld x = x.Age >= 65

Actually, what seems to happen is that isOld will only accept cats:

let dog = { DogName = "Jackie"; Age = 4 }
let cat = { CatName = "Micky"; Age = 80 }

let isDogOld = isOld dog //error

My hopes were that F# would be smart enough to define some kind of "virtual" interface X for both cats and dogs so that isOld would accept a X as argument, instead of a Cat.

This isn't something that F# will in any circumstance handle, am I right? It seems like F# type inference system would not do anything more than what the C# does with var typed variables.

like image 812
devoured elysium Avatar asked Aug 15 '11 14:08

devoured elysium


2 Answers

You can define an inline function with a member constraint, or go the classic route and use an interface (which would probably be preferred in this case).

let inline isOld (x:^T) = (^T : (member Age : int) x) >= 65

EDIT

I just remembered this won't work for record types. Technically their members are fields, although you can amend them with members using with member .... You would have to do that to satisfy an interface anyway.

For reference, here's how you would implement an interface with a record type:

type IAging =
  abstract Age : int

type Dog = 
  { DogName : string
    Age : int } 
  interface IAging with
    member this.Age = //could also be `this.Age = this.Age`
      let { DogName = _; Age = age } = this
      age
like image 115
Daniel Avatar answered Oct 05 '22 22:10

Daniel


Usually what is meant by F# duck-typing is a compile-time polymorphism. Syntax is a little weirder, but you should be able to work it out from the following example -

module DuckTyping

// Demonstrates F#'s compile-time duck-typing.

type RedDuck =
    { Name : string }
    member this.Quack () = "Red"

type BlueDuck =
    { Name : string }
    member this.Quack () = "Blue"

let inline name this =
    (^a : (member Name : string) this)

let inline quack this =
    (^a : (member Quack : unit -> string) this)

let howard = name { RedDuck.Name = "Howard" }
let bob = name { BlueDuck.Name = "Bob" }
let red = quack { RedDuck.Name = "Jim" }
let blue = quack { BlueDuck.Name = "Fred" }

Remember, this polymorphism only works at compile-time!

like image 30
Bryan Edds Avatar answered Oct 05 '22 21:10

Bryan Edds