假设我在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
1}}。
这不是F#在任何情况下处理的事情,对吗?似乎F#类型推理系统不会做任何比C#对var
类型变量做的更多的事情。
答案 0 :(得分:14)
您可以使用成员约束定义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
答案 1 :(得分:7)
通常F#duck-typing的含义是编译时多态。语法有点怪异,但您应该能够从以下示例中解决它 -
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" }
请记住,这种多态性仅适用于编译时!
答案 2 :(得分:3)
FSharp.Interop.Dynamic(在nuget上)提供基于DLR的动态运算符实现(真正的动态鸭子类型)
let isOld x = x?Age >= 65