【问题标题】:F# '+' operator overload and List.foldF# '+' 运算符重载和 List.fold
【发布时间】:2012-01-10 01:36:04
【问题描述】:

我正在尝试在为 + 定义运算符重载的记录类型上使用 List.fold,但在尝试使用 (+) 运算符作为传递给 fold 的 lambda 时出现类型不匹配错误.这是一个简化的 sn-p 示例,可以说明我的问题:

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

【问题讨论】:

  • 您的+ 成员毫无意义。为什么将两个人相加会返回他们的年龄总和?
  • 你在下面得到了很多很好的解释。我建议您自己尝试编写 fold 函数,看看为什么您不应该期望它的行为方式与您想象的一样。
  • @ChaosPandion:这个例子是一个展示原理的愚蠢设置——这不是我真正想要做的——我做了一个愚蠢的例子来清楚地展示这个问题。
  • @Kevin Won - 你能在你认为这样的操作有意义的地方展示代码吗?
  • @Kevin - 我不同意你在“另一个编辑”部分的结论。您可以像使用内置运算符一样使用(+) 运算符,只要它有意义,F# 就会识别和使用它而不会出现任何问题(例如List.map2 (+) [jen] [kevin])。在这种情况下,问题是您尝试使用 int 种子、person -> person -> int 累加器和 person list,这与 fold 的工作方式不兼容。

标签: f# overloading record operator-keyword fold


【解决方案1】:

List.fold 函数采用 State -> T -> State 类型的 lambda / 函数。在这种情况下,+ 运算符的类型为 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

【讨论】:

  • 谢谢。您的解决方案的问题是您没有使用已经在“+”运算符重载中编写的功能。我理解类型不匹配——我不想做的是必须重写 lambda 来完成我已经用运算符重载编写的内容。我的问题是:有可能吗?如果有,怎么做?
  • @KevinWon 只要您编写的运算符与折叠函数签名不兼容,这是不可能的。使其工作的唯一方法是,如果您的 + 接受 2 个 Person 对象并返回 Person
【解决方案2】:

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

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

【讨论】:

    【解决方案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
    

    【讨论】:

      【解决方案4】:

      这个 (+) 过载怎么样?

      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
      

      【讨论】:

      • 将多个人表示为一个名为 Person 的类型似乎有点不诚实。
      • @ChaosPandion:从具体类型中抽象可能有助于接受我的建议;在一天结束时int + int 给了int,而不是ints :)
      • 我想我正在理解+ 的语义。一般来说,它在处理数字时意味着求和,在处理其他任何东西时意味着连接。所以在我看来,当我将+ 应用于两个人时,结果是一个包含两个操作数的集合。
      • @ChaosPandion:说真的,我完全同意你对原始问题的评论。为什么在查找一组人的年龄时重载类型的操作[jen; kevin] |> List.fold (fun acc x -> acc + x.Age) 0 就足够了?
      【解决方案5】:

      问题是 + 是为添加整数、浮点数等定义的,而您为添加 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
      

      ...

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-02-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-25
        • 2016-02-19
        相关资源
        最近更新 更多