Метод 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 подчеркивает неправильную (неактуальную) вещь. Либо это, либо я просто не привык к этому.
Ответы
Ответ 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 #), чтобы вложенные вызовы функций выглядели скорее как цепочка преобразований.