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

Метод Chaining vs |> Трубный оператор

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

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

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

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

4b9b3361

Ответ 1

Конвейеризация поддерживает вывод 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)

Ответ 2

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

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

Ответ 3

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

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

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

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

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

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

ГДж

Ответ 4

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

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)

Ответ 5

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

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

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

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

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