17

假设我在 F# 中定义了以下两种类型:

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

我期待以下方法适用于猫和狗:

let isOld x = x.Age >= 65

实际上,似乎发生的事情是isOld只接受猫:

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

let isDogOld = isOld dog //error

我希望 F# 足够聪明,可以X为猫和狗定义某种“虚拟”接口,以便isOld接受 X 作为参数,而不是Cat.

这不是 F# 在任何情况下都可以处理的事情,对吗?似乎 F# 类型推断系统不会做比 C# 对var类型变量所做的更多的事情。

4

3 回答 3

19

您可以定义一个inline带有成员约束的函数,或者走经典路线并使用一个接口(在这种情况下可能是首选)。

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

编辑

我只记得这不适用于记录类型。从技术上讲,它们的成员是字段,尽管您可以使用with member .... 无论如何,您都必须这样做才能满足接口。

作为参考,以下是实现具有记录类型的接口的方法:

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
于 2011-08-15T14:21:44.657 回答
8

通常 F# 的鸭子类型是指编译时多态性。语法有点奇怪,但您应该能够从以下示例中解决它 -

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" }

请记住,这种多态性仅在编译时有效!

于 2014-02-10T05:54:26.400 回答
4

FSharp.Interop.Dynamic(在 nuget 上)提供了基于 DLR 的动态运算符实现(真正的动态鸭子类型)

let isOld x = x?Age >= 65
于 2011-08-15T14:41:02.197 回答