似乎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)
多个类型可以有一个成员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)
在printPerson,有关编译器的唯一信息p是它有一个Name成员.由于可以有其他类型的比Person它有一个,它不能推断p是Person.
在printPerson类型的调用中,p从模式确定,而不是从调用中确定.
如果您正在使用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)