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

Dev*_*ewb 4 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
Run Code Online (Sandbox Code Playgroud)

Jak*_*man 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.
Run Code Online (Sandbox Code Playgroud)

编译器无法分辨你的意思 - Person或者School.如果你没有定义具有相同名称的成员的另一个类型并不重要,编译器仍然不会采用类型,因为类型扩展之类的东西可以在编译后将成员添加到类型上.

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

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

let printPerson level p =
Run Code Online (Sandbox Code Playgroud)

let printPerson level (p : Person) =
Run Code Online (Sandbox Code Playgroud)

  • @DevNewb有一篇关于"F#for fun and profit"的好文章,[这里](https://fsharpforfunandprofit.com/posts/type-in​​ference/) - 特别是标题为"信息不足"的部分. (2认同)
  • @DevNewb不幸的是在这种情况下 - 你给出的例子是有效的,因为函数"定义"(一个松散的术语,因为它是一个lambda)与第一次使用同时出现.假设您希望`printPerson`可重用(并且您应该,因为您使用它两次),您必须在使用它之前定义它,这意味着它需要一个类型注释.遗憾的是,不是那么整洁,而是在这种情况下我们能做到的最好. (2认同)
  • @DevNewb:更准确地说,它的工作原理是因为`s`的类型是从管道运算符左侧列表的类型推断出来的.如果你遍历一个'Persons`列表并在那里内联你的`printName`函数,你也不需要注释. (2认同)

Ale*_*nov 5

printPerson,有关编译器的唯一信息p是它有一个Name成员.由于可以有其他类型的比Person它有一个,它不能推断pPerson.

printPerson类型的调用中,p从模式确定,而不是从调用中确定.


The*_*ght 5

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

您将更改您的定义Person如下:

type Person = {Name : string}
Run Code Online (Sandbox Code Playgroud)

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

let printPerson level p = 
    printfn "%s %s" <| String.replicate (level) "#" <| p.Name
Run Code Online (Sandbox Code Playgroud)

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


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

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

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