F #: уничтожение избыточности в Map / Reduce / Filter - PullRequest
13 голосов
/ 10 октября 2011

Допустим, у меня есть список и набор, и я хочу с ними кое-что сделать:

let outA = inA |> List.map(fun x -> x + 1) |> List.filter(fun x -> x > 10)
let outB = inB |> Set.map(fun x -> x + 1) |> Set.filter(fun x -> x > 10)

Теперь, очевидно, A обрабатывает списки, а B - наборы.Тем не менее, я нахожу очень раздражающим то, что приходится писать List. List. снова и снова: это не только подробный, повторяющийся шаблон, который не передает никакой информации и мешает чтению функциональности кода, но иФактическая аннотация типа, которую я должен отслеживать и синхронизировать с остальной частью моего кода.

То, что я хочу сделать, это что-то вроде следующего:

let outA = inA |> map(fun x -> x + 1) |> filter(fun x -> x > 10)
let outB = inB |> map(fun x -> x + 1) |> filter(fun x -> x > 10)

Поскольку компилятор знает, что inA является списком, а inB является множеством, и, следовательно, все операции принадлежат правильному классу, и поэтому outA является списком, а outB - множеством.Я могу частично добиться этого с помощью Seq:

let map(x) =
    Seq.map(x)

let filter(x) =
    Seq.filter(x)

И я могу написать именно это.Проблема с этим в том, что он преобразует все в последовательности, и я больше не могу выполнять операции над списком / установкой над ними.Точно так же

let outA = inA.Select(fun x -> x + 1).Where(fun x -> x > 10)
let outB = inB.Select(fun x -> x + 1).Where(fun x -> x > 10)

также удаляет все это, но затем приводит все к IEnumerable.Мне удалось получить это довольно хорошо, преобразовав все статические методы в методы экземпляра с помощью расширений:

type Microsoft.FSharp.Collections.List<'a> with
    member this.map(f) = this |> List.map(f)
    member this.filter(f) = this |> List.filter(f)

let b = a.map(fun x -> x + 1).filter(fun x -> x > 10)

, но я подозреваю, что возникнут проблемы, связанные с выводом типов, упомянутые здесь: Метод Chaining vs |> Трубный оператор ?Я действительно не знаю;Я не знаком с тем, как работает алгоритм вывода типов.

Суть в том, что я полагаю, что собираюсь выполнять чертовски много из этих операций списка / набора / массива / уменьшения / фильтрации иЯ хочу, чтобы они выглядели максимально красиво и чисто.Прямо сейчас, кроме отвлечения меня от важных битов в выражении (то есть, «карта» и лямбда), они также предоставляют фактические аннотации типа в месте, где компилятор должен хорошо знать, что такое коллекция.прохождение в том.

Более того, это именно та вещь, которую должны решить наследование и полиморфизм ОО, поэтому мне было интересно, если бы существовал какой-то эквивалентный функциональный паттерн, который я не знаю, который бы решал его так же элегантно.Возможно, я мог бы выполнить проверку типа в своем собственном статическом map и выполнить функцию map соответствующего типа на входе?

Есть ли у кого-нибудь лучшее решение, чем те, которые я уже пробовал, или яврезаться мне в принципиальное ограничение механизма логического вывода типа F #, что я должен просто принять и двигаться дальше?

Ответы [ 2 ]

11 голосов
/ 10 октября 2011

Это не то, что решает ОО / наследование / полиморфизм.Скорее это то, что решают «классы типов» (а-ля Хаскелл).Система типов .NET не способна выполнять классы типов, и F # не пытается добавить эту возможность.(Вы можете сделать несколько хитрых трюков с inline, но у него есть различные ограничения.)

Я рекомендую принять его и двигаться дальше.

5 голосов
/ 10 октября 2011

Я согласен с Брайаном - вы просто привыкнете к этому.Как я уже упоминал в ответе на ваш предыдущий вопрос, это иногда весьма полезно, потому что вы увидите, что делает ваш код (какая часть вычисляется лениво, какая часть использует массивы для эффективности и т. Д.)

Кроме того, некоторыефункции доступны только для некоторых типов - например, есть List.tail или List.foldBack, но аналогичные функции не существуют для IEnumerable (в модуле Seq) - по веским причинам, поскольку они могут привести к некорректному коду.

В Haskell классы типов могут описывать типы, которые имеют некоторые функции (например, map и filter), но я не думаю, что они масштабируются очень хорошо - чтобы указать классы типов для библиотеки F #,в итоге вы получите иерархию классов типов, которые задают структуры, подобные списку, структуры данных, подобные списку, с функциями, подобными tail и foldBack, и слишком много других ограничений.

Кроме того, для многих простых заданий по обработке списков вы также можете использовать понимание, которое дает вам более приятный синтаксис (но, конечно, это полезно только для базовых вещей):

let outB = seq { for x in inA do 
                   if x > 10 do yield x + 1 }
...