Подтвердить что ты не робот

F #: избыточность убийства в Map/Reduce/Filter

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

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)

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

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

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

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

4b9b3361

Ответ 1

Это не то, что решает OO/наследование/полиморфизм. Скорее это то, что "классы типа" (a la Haskell) разрешают. Система типа .NET не способна выполнять классы типов, а F # не пытается добавить эту возможность. (Вы можете сделать некоторые волосатые трюки с inline, но у него есть различные ограничения.)

Я рекомендую принять его и продолжить.

Ответ 2

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

Кроме того, некоторые функции доступны только для некоторых типов - например, есть 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 }