2

我正在尝试在为 定义运算符重载的记录类型上使用 List.fold +,但是在尝试将(+)运算符用作传递给 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没有无缝的方法可以让+作为 lambda 传递的工作就像用作中缀 ala 时那样工作jen + kevin。为什么这不起作用是完全有道理的。人们建议解决这个问题的解决方案基本上是一次性处理特定问题的fold——我真正追求的是如何获得正确的运算符重载以在每种情况下都被选中(即foldback等)——我不想写一堆特殊情况的代码来处理列表。很明显,F# 的运算符重载解决方案有一些限制,使其工作到肤浅的水平,这很好。

4

5 回答 5

7

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

要折叠年龄,请尝试以下操作

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

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

people
|> Seq.ofList
|> Seq.map (fun p -> p.Age)
|> Seq.fold (+) 0
于 2012-01-10T01:44:19.423 回答
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
于 2012-01-10T02:34:39.547 回答
3

我认为你的问题是概念性的。你传递给List.fold的是一个函数。最好将其+视为一整套不同函数的语法糖 - 具有类型签名,如int -> int -> int,float -> 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
于 2012-01-10T02:50:40.230 回答
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
于 2012-01-10T02:28:28.830 回答
1

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

 List.fold (+) 0 people;;

你正在尝试添加一个 int (0) 和一个 Person (people) 是!..

实际上,当您添加 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

...

于 2012-01-21T15:06:15.823 回答