F#'+'运算符重载和List.fold

时间:2012-01-10 01:36:05

标签: f# overloading record operator-keyword fold

我正在尝试在定义+的运算符重载的记录类型上使用List.fold,但在尝试使用(+)运算符时遇到类型不匹配错误lambda传递给折叠。这是一个简化的代码片段,可以解释我的问题:

// a record type that also includes an overload for '+'
type Person = 
    { Name : string; Age: int }
    static member ( + ) (x: Person, y: Person) = x.Age + y.Age

+重载工作正常

> jen + kevin;;
val it : int = 87

但是说我有个人名单:

> let people = [kevin;jen];;

我不能使用List.fold来累计所有年龄:

> List.fold (+) 0 people;;

List.fold (+) 0 people;;
----------------^^^^^^

error FS0001: Type constraint mismatch. The type 
    int    
is not compatible with type
    Person    
The type 'int' is not compatible with the type 'Person'

我猜测问题是F#在以这种方式传递时无法辨别+的重载,因为fold隐式地将列表键入int,因为我使用'0 '作为累加器。我不确定是否有可能让我的自定义操作符重载正常工作,如果可能的话,我错过了让它发生的事情。 (我假设可以使这项工作成功,因为你可以在浮点数上使用+

修改

我理解问题是类型不匹配。正如JaredPar所写,我知道我可以写一个lambda来获取两个人的记录并添加年龄。那不是我的观点。问题是在我看来应该有一种方法可以让我已经编写的+运算符重载被fold确认为有效的重载。

另一个编辑

感谢大家的投入。有一件事情已经变得清晰了,不是可能做我想做的事,但那没关系。我学到了什么!我所看到的是,运算符重载的解决方案使得它们不能在每个上下文中工作 - 因此fold没有无缝方式使{{1}作为一个lambda工作传递就像用作中缀ala +时那样。这完全有道理,为什么这不起作用。人们建议解决这个问题的解决方案基本上是一次性处理jen + kevin的特定问题 - 我真正想到的是如何获得正确的运算符重载以获取每个< / strong>情况(即fold等) - 我不想写一堆特殊案例代码来处理列表。很明显不是F#的运算符重载分辨率有一些限制,使其适用于皮肤深层次,这很好。

5 个答案:

答案 0 :(得分:7)

List.fold函数采用State -> T -> State类型的lambda /函数。在这种情况下,+运算符的类型Person -> Person -> int与签名不兼容。这就是你收到错误的原因。

要折叠年龄,请尝试以下

people |> List.fold (fun sum p -> sum + p.Age) 0

在此处使用+运算符作为折叠的一部分的一种方法是将Person映射到Age属性,然后对int +运算符使用fold。

people
|> Seq.ofList
|> Seq.map (fun p -> p.Age)
|> Seq.fold (+) 0

答案 1 :(得分:4)

这是一个可能对您有用的合理解决方案。

type Person = { 
    Name : string
    Age: int 
} with
    static member (+) (x: Person, y: Person) = 
        { Set = Set.ofList [x; y]; SumOfAges = x.Age + y.Age }

and People = { 
    Set:Person Set
    SumOfAges:int
} with
    static member (+) (x:People, y:Person) = 
        { x with Set = x.Set.Add y; SumOfAges = x.SumOfAges + y.Age }
    static member Empty = 
        { Set = Set.empty; SumOfAges = 0 }

let p = [ { Name = "Matt"; Age = 32; }; { Name = "Dan"; Age = 26; } ]
let r = p |> List.fold (+) People.Empty

答案 2 :(得分:3)

我认为你的问题是概念性的。传递给List.fold的是一个单一的功能。最好将+视为一堆不同函数的语法糖 - 使用int -> int -> intfloat -> float -> floatperson -> person -> int等类型签名。

那么当编译器看到这种情况时会发生什么:?

List.fold (+) 0 people;;

因此,我们有一个person列表以及一个0的默认参数int。所以我们看一下fold

的签名
List.fold : ('State -> 'T -> 'State) -> 'State -> 'T list -> 'State

根据'State = int,解释此问题的一种方法可能是0。因此,我们需要找到+的重载,看起来像

int -> Person -> int

这当然不存在。然后,您可以使用它为+运算符提供更好的定义。像

这样的东西
// a record type that also includes an overload for '+'
type Person = 
    { Name : string; Age: int }
    static member ( + ) (x: int, y: Person) = x + y.Age

答案 3 :(得分:2)

这个(+)超载怎么样?

type Person =
      { Name : string; Age: int }
      static member ( + ) (x: Person, y: Person) = { Name = x.Name + " and " + y.Name; Age = x.Age + y.Age }

let jen = { Name = "Jen"; Age = 20 }
let kevin = { Name = "Kevin"; Age = 40 }

[jen; kevin] |> List.fold (+) { Name = ""; Age = 0 };;

将返回

val it : Person = {Name = "Jen and Kevin";
                   Age = 60;}

有道理吗?

但严重的是,如果您认为找到一组人的摘要年龄是您Person课程不可或缺的一部分,您可以考虑将相应的静态班级成员GroupAge设为(+)而不是重载type Person = { Name : string; Age: int } static member GroupAge = List.fold (fun age person -> age + person.Age) 0

[jen; kevin] |> Person.GroupAge

并在需要时使用它,如下所示:

{{1}}

答案 4 :(得分:1)

问题是+是为添加整数,浮点数等定义的,你定义一个+为2加人..但是当你尝试:

 List.fold (+) 0 people;;

你正在尝试添加一个人(人)的int(0) 那是!..

实际上当你添加2个人时,这会返回一个整数...然后每次比你迭代的人都会得到累积(整数)和人(人名单)......

现在......解决这个问题的简单方法是在不添加更多重载或泛型的情况下尝试:

[kevin;jen] |> List.fold (fun acc person -> acc + person.Age) 0

类似于http://msdn.microsoft.com/en-us/library/dd233224.aspx

中的示例
let data = [("Cats",4);
        ("Dogs",5);
        ("Mice",3);
        ("Elephants",2)]
let count = List.fold (fun acc (nm,x) -> acc+x) 0 data
printfn "Total number of animals: %d" count

...