F# 为什么可以';在这种情况下,编译器推断类型是什么?

F# 为什么可以';在这种情况下,编译器推断类型是什么?,f#,type-inference,F#,Type Inference,printPerson函数中的p参数似乎不能被推断为Person,但intelissense显示,对于两个printPerson调用,我都在传递p:Person。请帮我理解我做错了什么 type Person (name:string) = member e.Name = name type EmployeeNode = | Leader of Person * int * list<EmployeeNode> | Employee of Per

printPerson函数中的p参数似乎不能被推断为Person,但intelissense显示,对于两个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
type Person(姓名:string)=
成员e.名称=名称
类型EmployeeNode=
|人员*int*列表的负责人
|个人的雇员*int
让printPerson级别p=
打印fn“%s%s”
打印人员p级
List.iter打印节点

printPerson
中,关于
p
编译器的唯一信息是它有一个
名称
成员。因为除了
Person
之外,还可能有其他类型的人有一个,所以不能推断
p
Person


在调用
printPerson
时,
p
的类型由模式决定,而不是由调用决定。

多个类型可能有一个成员
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.
编译器无法分辨您指的是人还是学校。如果没有使用相同名称的成员定义另一个类型并不重要,编译器仍然不会接受该类型,因为类型扩展之类的东西可以在编译后将成员添加到类型中


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

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

let printPerson level p =


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

您将更改
人的定义,如下所示:

type Person = {Name : string}
如果使用记录,则不需要使用类型批注,并且可以保持代码的原样:

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

“一般来说,任何访问类方法或成员的函数都需要该类的类型注释。”有什么地方我可以读到更多关于这方面的内容吗?@DevNewb有一篇关于“F#for fun and price”的好文章,特别是标题为“信息不足”的部分。啊,请看,但在这里,我们可以访问类成员长度而无需注释:
[“hello”;“world”]|>List.map(fun s->s.Length)//确定
有什么方法可以修改我的代码来做到这一点吗?我试过了,但是failed@DevNewb不幸的是,不是在这种情况下-您给出的示例有效,因为函数“definition”(一个松散的术语,因为它是lambda)与第一次使用同时出现。假设您希望
printPerson
可重用(而且您应该这样做,因为您使用了它两次),那么您必须在使用它之前定义它,这意味着它需要一个类型注释。遗憾的是,它没有那么整洁,但在这种情况下我们只能做到最好。@DevNewb:更准确地说,它之所以有效,是因为
s
的类型是从管道操作符左侧的列表类型推断出来的。如果您在
人员列表
上迭代,并在那里内联
printName
函数,您也不需要注释。我很惊讶类型推断在这里起作用,老实说,我不喜欢这样做(但我确实喜欢建议更喜欢记录)。但请注意,您需要小心记录字段名冲突。例如,如果您还有
typeperson2={Name:string;Age:int}
F#将推断
printPerson
的哪个更晚(您仍然可以使用注释来指定)。因此,在一个大型程序中,在一个文件中添加一条记录可能会意外地更改在别处定义的函数的类型。@Alexey这是一个很好的观点,但类型推断是否仅限于打开的模块?这仍然是个问题,但要少一些severe@AlexeyRomanov我只是感到惊讶的是,目前对这一点的记录似乎很糟糕,因为这一直是记录相对于标准类的主要优势之一,但您是对的,在重新使用相同的属性名时需要小心。@DevNewb没错,智能感知类型推理必然比编译时类型推理更模糊。
let printPerson level p = 
    printfn "%s %s" <| String.replicate (level) "#" <| p.Name
type Person (name:string) = 
    member e.Name = name

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