在这种情况下,为什么F#编译器不能推断类型?

时间:2016-11-28 19:49:23

标签: f# type-inference

似乎printPerson函数中的p参数不能被推断为Person,但是intelisense显示我正在传递的两个printPerson调用p:Person。请帮我理解我做错了什么?

type Person (name:string) = 
        member e.Name = name

type EmployeeNode = 
    | Leader of Person * int * list<EmployeeNode>
    | Employee of Person * int

let printPerson level p = 
    printfn "%s %s" <| String.replicate (level) "#" <| p.Name 

let rec print node  = 
    match node with
    | Employee(p, level) -> printPerson level p
    | Leader(p, level, nodes) -> 
        printPerson level p
        List.iter print nodes

3 个答案:

答案 0 :(得分:6)

多个类型可以拥有成员this.Name,即使他们不在此示例中,因此编译器也不知道您的意思是Person。比如说你有

type Person (name : string) =
    member this.Name = name

type School (name : string, address : string) =
    member this.Name = name
    member this.Address = address

let printName x = printfn "%s" x.Name       // This is a type error.

编译器无法确定您的意思 - PersonSchool。如果你没有定义具有相同名称的成员的另一种类型,那么编译器仍然不会采用该类型并不重要,因为类型扩展之类的东西可以在编译之后将成员添加到类型上。 / p>

Intellisense知道您在调用函数时尝试传递的类型,但这与编译器强制类型安全性不同。通常,任何访问类方法或成员的函数都需要该类的类型注释。

要修复您的示例,您只需要更改为

let printPerson level p =

let printPerson level (p : Person) =

答案 1 :(得分:5)

printPerson中,有关编译器p信息是它有Name成员。由于可能存在除Person之外的其他类型,因此无法将p推断为Person

printPerson的来电中,p的类型是根据模式确定的,而不是来自通话。

答案 2 :(得分:5)

如果您正在使用F#中的简单不可变数据容器,您会发现编写惯用代码更容易,使用类型推断,使用Records而非标准.NET类。

您可以按如下方式更改Person的定义:

type Person = {Name : string}

如果使用记录,则不需要使用类型注释,并且可以保持代码不变:

let printPerson level p = 
    printfn "%s %s" <| String.replicate (level) "#" <| p.Name

我会推荐这种方法,特别是因为记录可以免费提供额外的奖励,例如自动结构平等和比较。

如果您使用标准.NET类,则必须提供类型注释以消除您所引用的特定类型的歧义,该类型公开Name属性:

type Person (name:string) = 
    member e.Name = name

let printPerson level (p : Person) = 
    printfn "%s %s" <| String.replicate (level) "#" <| p.Name