Цепочка методов против |> Трубный оператор - PullRequest
22 голосов
/ 08 октября 2011

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

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

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

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

Ответы [ 5 ]

22 голосов
/ 08 октября 2011

Конвейер поддерживает вывод типа 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)
14 голосов
/ 08 октября 2011

Другие уже объяснили большинство различий между двумя стилями. С моей точки зрения, наиболее важным является вывод типа (упомянутый Даниэлем), который лучше работает с идиоматическим стилем 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 #.

8 голосов
/ 08 октября 2011

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

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

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

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

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

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

GJ

3 голосов
/ 08 октября 2011

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

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)
2 голосов
/ 08 октября 2011

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

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

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

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

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

...