F # '+' оператор перегрузки и List.fold - PullRequest
2 голосов
/ 10 января 2012

Я пытаюсь использовать List.fold для типа записи, который определяет перегрузку оператора для +, но я получаю ошибку несоответствия типов при попытке использовать оператор (+), поскольку лямбда-выражение передается для свертывания,Вот упрощенный фрагмент, который иллюстрирует мою проблему:

// 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' в качестве аккумулятора.Я не уверен, возможно ли заставить мою пользовательскую перегрузку оператора работать правильно, и если это возможно, то, чего мне не хватает, чтобы это произошло.(Я предполагаю, что это возможно сделать, потому что вы можете использовать + для чисел с плавающей запятой).

edit

Я понимаю, что проблема заключается в несоответствии типов.Как пишет JaredPar, я понял, что могу написать лямбду, чтобы взять записи двух человек и добавить возраст.Это не моя точка зрения.Проблема в том, что мне кажется, что должен быть способ получить перегрузку оператора +, которую я уже написал, чтобы она была подтверждена Fold как допустимая перегрузка.

другое редактирование

Спасибо всем за ваш вклад.Одна вещь, которая становится ясной, это то, что не невозможно делать то, что я хочу, но это нормально.Я кое-что узнал!Я вижу, что разрешение перегрузок операторов таково, что они не работают во всех контекстах, поэтому с fold не существует бесшовного способа сделать + переданным как лямбдаработать так же, как и при использовании в качестве инфикса аля jen + kevin.Это имеет полный смысл, почему это не работает правильно.Решения, которые люди предложили для решения этой проблемы, в основном являются однократными и решают конкретную проблему fold - я действительно хочу узнать, как получить правильную перегрузку оператора для выбора каждые ситуация (т. е. foldback и т. д.) - мне не хотелось писать кучу кода специального случая для работы со списками.Совершенно ясно, не то, что разрешение перегрузки оператора F # имеет некоторые ограничения, которые заставляют его работать на уровне скинов, и это нормально.

Ответы [ 5 ]

7 голосов
/ 10 января 2012

Функция List.fold принимает лямбда / функцию типа 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
4 голосов
/ 10 января 2012

Вот разумное решение, которое может быть полезно для вас.

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 голосов
/ 10 января 2012

Я думаю, что ваша проблема носит концептуальный характер.То, что вы передаете List.fold, является единственной функцией.Лучше всего рассматривать + как синтаксический сахар для целого стека различных функций - с сигнатурами типов, такими как int -> int -> int, float -> float -> float и person -> 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
2 голосов
/ 10 января 2012

Как насчет этой (+) перегрузки?

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 голос
/ 21 января 2012

проблема в том, что + определяется для добавления целых чисел, чисел с плавающей точкой и т. Д., И вы определяете + для добавления 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

...

...