Цепочка методов против оператора |› Pipe

Итак, у меня есть следующий код:

// Learn more about F# at http://fsharp.net
open System
open System.Linq
open Microsoft.FSharp.Collections

let a = [1; 2; 3; 4; 54; 9]

let c = a |> List.map(fun(x) -> x*3) |> List.filter(fun(x) -> x > 10)
let d = a.Select(fun(x) -> x*3).Where(fun(x) -> x > 10)

for i in c do
    Console.WriteLine(i)

for i in d do
    Console.WriteLine(i)

Оба, кажется, делают одно и то же, но в большинстве примеров F#, которые я вижу, используется оператор канала |>, в то время как я больше привык к цепочке методов (a.l.a. C# Linq). Последний также несколько короче, хотя и несколько более сжат вместе. Пока я использую синтаксис C# Linq, но это скорее привычка/инерция, чем какое-либо реальное дизайнерское решение.

Есть ли какие-то соображения, о которых я должен знать, или они в основном идентичны?

Изменить. Другое соображение заключается в том, что синтаксис Pipe значительно более «шумный», чем синтаксис Linq: операция, которую я делаю (например, «карта»), очень короткая и написана строчными буквами, а каждой из них предшествует этот Огромный «|> List», который, помимо увеличения длины, отвлекает внимание от крошечного имени метода в нижнем регистре. Даже подсветка синтаксиса StackOverflow выделяет неправильные (нерелевантные) вещи. Либо так, либо я просто не привык к этому.


person Li Haoyi    schedule 08.10.2011    source источник


Ответы (5)


Конвейерная обработка поддерживает вывод типов F# слева направо. a.GroupBy требует, чтобы тип a уже был известен как seq<_>, тогда как a |> Seq.groupBy сам выводит a как seq<_>. Следующая функция:

let increment items = items |> Seq.map (fun i -> i + 1)

требует, чтобы аннотация типа была написана с использованием LINQ:

let increment (items:seq<_>) = items.Select(fun x -> x + 1)

Когда вы освоитесь с функциональным стилем, вы найдете способы сделать свой код более кратким. Например, предыдущую функцию можно сократить до:

let increment = Seq.map ((+) 1)
person Daniel    schedule 08.10.2011
comment
Хороший пример @Daniel. Вот почему я написал выглядят более естественными, или, лучше сказать, они больше похожи на вариант C#. Я предпочитаю более функциональный стиль при работе с F# (вероятно, из-за моего предыдущего опыта работы с ML), поэтому я предпочитаю вашу последнюю функцию. Это дело вкуса и командного стиля IMO - person Lorenzo Dematté; 08.10.2011
comment
@Daniel: Интересный компромисс. Я читал ваш пост ранее, но только что понял, каковы последствия. По сути, Seq.map равнозначно объявлению items как seq перед использованием, в то время как items.map ничего не говорит вам о items. Означает ли это, что даже если вы позже поместите известную последовательность в функцию increment, механизм вывода типов не вернется к определению increment и не решит, что он принимает seq? - person Li Haoyi; 10.10.2011
comment
@LiHaoyi: хотя последующее использование может ограничить тип функции, ваша функция может взаимодействовать только с тем типом, который известен в момент объявления вашей функции. Вывод типа работает сверху вниз и слева направо, поэтому компилятор может использовать только информацию о типе, доступную до точки использования. - person Daniel; 10.10.2011
comment
@Daniel: Есть ли шанс, что вы могли бы немного распаковать свою последнюю версию? У меня есть смутное представление о том, что происходит, но я не понимаю, почему это сработает, и я подозреваю, что знание того, почему это может повлиять на другие концепции; например карри... - person Phil H; 24.04.2014
comment
@PhilH: это частичное применение Seq.map. Seq.map ожидает два аргумента, функцию и последовательность; мы предоставляем функцию. (+) — функция, принимающая два аргумента; мы поставляем первый (1). Другим аргументом будет каждый элемент последовательности. Это помогает? - person Daniel; 24.04.2014
comment
@Daniel: Да, спасибо. Таким образом, функция (+) каррируется для создания функции отображения 1:1, и она передается в карту, которая принимает ее в качестве первого аргумента. Создавая это с помощью let, мы каррируем первый аргумент, оставляя второй аргумент (а именно, последовательность для сопоставления), так что теперь это функция, принимающая 1 аргумент (последовательность) и возвращающая сопоставленную последовательность, я думаю. - person Phil H; 28.04.2014

Другие уже объяснили большую часть различий между двумя стилями. С моей точки зрения, наиболее важным является вывод типов (упомянутый Дэниелом), который лучше работает с идиоматическим стилем F#, основанным на конвейерной обработке и таких функциях, как List.map.

Еще одно отличие заключается в том, что при использовании стиля F# вы можете легче увидеть, какая часть вычислений выполняется лениво, когда вычисление принудительно и т. д., поскольку вы можете комбинировать функции для IEnumerable<_> (называемые Seq) и функции для списков или массивов:

let foo input =
  input 
  |> Array.map (fun a -> a) // Takes array and returns array (more efficient)
  |> Seq.windowed 2         // Create lazy sliding window
  |> Seq.take 10            // Take sequence of first 10 elements
  |> Array.ofSeq            // Convert back to array

Я также нахожу оператор |> синтаксически более удобным, потому что я никогда не знаю, как правильно сделать отступ в коде, использующем .Foo, особенно где поставить точку. С другой стороны, |> имеет устоявшийся стиль программирования на F#.

В общем, я рекомендую использовать стиль |>, потому что он "более стандартный". Нет ничего плохого в использовании стиля C# в F#, но вы можете обнаружить, что написание кода в более идиоматическом стиле упрощает использование некоторых интересных концепций функционального программирования, которые лучше работают в F#, чем в C#.

person Tomas Petricek    schedule 08.10.2011
comment
Я знаю, что это фиктивный пример, но будет ли когда-нибудь причина использовать функцию Array.map, подобную той, которую вы показываете? - person Mike K; 11.10.2011
comment
не по теме, но мне бы очень хотелось, чтобы мы когда-нибудь получили подобную цепочку методов в C++. - person Viktor Sehr; 21.08.2013

На самом деле оператор канала ничего не делает, кроме как меняет местами функцию и аргумент, насколько мне известно, нет никакой разницы между f1 (f2 3) и 3 |> f2 |> f1, кроме того, что последний легче читать, когда вы много связываете вместе.

редактировать: это фактически определено так: let inline (|>) x f = f x.

Я предполагаю, что причина, по которой вы склонны видеть подход List.map больше, чем Linq, заключается в том, что в OCaml (предшественнике F#) эти операторы всегда были там, поэтому этот стиль кодирования действительно укоренился в том, как думают функциональные программисты. Список — это очень простая концепция в F#, она немного отличается от IEnumerable (это ближе к Seq).

Linq — это в значительной степени попытка перенести эти концепции функционального программирования на C# и VB. Итак, они есть на платформе .Net и поэтому доступны, но в F# они избыточны.

Кроме того, List.map — очень простая операция, в то время как подход Linq включает всю структуру с ленивой оценкой и т. д., что приводит к некоторым накладным расходам. Но я не думаю, что это будет иметь существенное значение, пока вы действительно не будете использовать его часто. Я слышал в каком-то разговоре, что причина, по которой компилятор C# больше не использует Linq, заключается в этой причине, но в обычной жизни вы вряд ли заметите.

В общем, делайте то, что вам удобнее, здесь нет правильного или неправильного. Лично я бы выбрал операторы списка, потому что они более стандартны в «идиоматическом» F#.

GJ

person gjvdkamp    schedule 08.10.2011
comment
seq<T> и IEnumerable<T> не просто близки, они одинаковы. seq<T> — это просто псевдоним для IEnumerable<T>. - person svick; 08.10.2011
comment
Подход LINQ на самом деле не включает всю структуру с ленивой оценкой. Это просто синтаксическая разница, и она зависит от реализации Select (которая не обязательно должна быть IEnumerable). Кроме того, вы можете получить ленивую оценку с помощью F #, используя Seq.map. - person Tomas Petricek; 08.10.2011
comment
Вы правы, я проверил с помощью ILSpy, и это всего лишь базовые счетчики. У меня всегда было впечатление, что связанные функции хранятся в виде дерева выражений и оптимизируются, компилируются и выполняются только тогда, когда запрашивается результат, я почти уверен, что получил это из выступления Linq на C9, когда он был впервые представлен. Может быть, я запутался с Linq to SQL. - person gjvdkamp; 08.10.2011
comment
@gjvdkamp Да, это происходит только в реализациях для доступа к базам данных (LINQ to SQL или Entities). Хитрость заключается в том, что метод Select ожидает аргумент типа Expression<Func<...>>, а не только Func<...>. Кроме того, использование этих методов из F # не будет работать, потому что цитаты F # работают иначе, чем деревья выражений C #. - person Tomas Petricek; 08.10.2011
comment
Хотя синтаксис похож, я бы не назвал OCaml предшественником — OCaml во многих отношениях мощнее, чем F# (например, это система модулей) - person Random Dev; 13.10.2014
comment
Несколько лет назад один приятель огорчил меня, потому что мой код VB выглядел как C. Есть старая шутка, что настоящий программист может написать FORTRAN на любом языке. Таким образом, моя задача состоит в том, чтобы написать F#, использующий функциональную парадигму. Linq — это шаг в правильном (декларативном) направлении от процедурного кода, но это компромисс на пути к функциональному мышлению и самовыражению на идиомах F#. - person StevePoling; 08.11.2018

Ну, одна вещь, с которой вы, вероятно, столкнетесь в конечном итоге, - это проблемы с выводом типов. Посмотрите, например, на этот пример:

open System
open System.Linq
open Microsoft.FSharp.Collections

let a = ["a", 2; "b", 1; "a", 42; ]

let c = a |> Seq.groupBy (fst) |> Seq.map (fun (x,y) -> x, Seq.length y)

//Type inference will not work here
//let d1 = a.GroupBy(fun x -> fst x).Select(fun x -> x.Key, x.Count())

//So we need this instead
let d2 = a.GroupBy(fun x -> fst x).Select(fun (x : IGrouping<string, (string * int)>) -> x.Key, x.Count())

for i in c do
    Console.WriteLine(i)

for i in d2 do
    Console.WriteLine(i)
person alun    schedule 08.10.2011

Насколько я понимаю, оператор F# |> был введен для того, чтобы операции последовательности выглядели как запросы LINQ, или, лучше, чтобы они были похожи на цепочку методов расширения C#. List.map и filter, по сути, являются функциями в «функциональном» смысле: получить последовательность и f на вход, вернуть последовательность. Без канала вариант F# будет

filter(fun(x) -> x > 10, map(fun(x) -> x*3, a))

Обратите внимание, что визуально порядок функций обратный (приложения все еще в том же порядке): с |> они выглядят более «естественно» или, лучше, они больше похожи на вариант C#. C# достигает той же цели с помощью методов расширения: помните, что метод C# на самом деле

Enumerable.Where(Enumerable.Select(a, f1), f2)

Enumerable.Select — это функция, в которой первым параметром является «this IEnumerable», который используется компилятором для преобразования его в a.Select... В конце концов, это языковые средства (реализованные посредством преобразований компилятора в C#, и использование операторов и частичного применения в F#), чтобы сделать вложенные вызовы функций более похожими на цепочку преобразований.

person Lorenzo Dematté    schedule 08.10.2011
comment
Но, учитывая, что я могу использовать стиль объединения методов расширения и в F#, следует ли мне по-прежнему придерживаться стиля конвейерной обработки F#, или это не имеет значения? Я думаю, что, вероятно, есть несколько забавных крайних случаев, о которых я не думаю, где их поведение отличается. - person Li Haoyi; 08.10.2011
comment
Оператор |> на самом деле не является преобразованием компилятора. Это обычный оператор F#. Это может работать таким образом благодаря частичному применению функций. - person svick; 08.10.2011
comment
@LiHaoyi, это зависит от вашей кодовой базы. Вероятно, разумнее использовать способ F#, так как другой код, который вы будете использовать (написанный другими, во внешних библиотеках и т. д.), вероятно, будет следовать шаблону F#. В противном случае, если вы работаете в команде, которая следует соглашениям C#, придерживайтесь их. - person Lorenzo Dematté; 08.10.2011