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.
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
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!
FSharp.Interop.Dynamic (on nuget) provides a DLR based implementation of the dynamic operator (real dynamic duck typing)
let isOld x = x?Age >= 65