map filter f# method-chaining c#-to-f#

map - Método Encadenamiento vs |> Operador de tubería



filter f# (5)

Así que tengo el siguiente código:

// 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)

Ambos parecen hacer lo mismo, pero la mayoría de los ejemplos de F # que veo usan el operador de tuberías, mientras que estoy más acostumbrado al encadenamiento de métodos (ala C # Linq). Este último también es algo más corto, aunque algo más aplastado. Por ahora estoy usando la sintaxis de C # Linq, pero eso es más de hábito / inercia en lugar de cualquier decisión de diseño real.

¿Hay alguna consideración que deba conocer o son básicamente idénticas?

Edit: La otra consideración es que la sintaxis de Pipe es significativamente más "ruidosa" que la sintaxis de Linq: la operación que estoy haciendo (por ejemplo, "map") es realmente corta y en minúsculas, mientras que cada una está precedida por esta Enorme "|> Enumere "eso, aparte de hacerlo más distrae el ojo del minúsculo nombre del método en minúscula. Incluso el resaltador de sintaxis de StackOverflow resalta lo incorrecto (irrelevante). O eso o simplemente no estoy acostumbrado.


A mi entender, el operador F # |> se introdujo para hacer que las operaciones de secuencia se vieran como las consultas LINQ, o mejor para que se vea similar al encadenamiento del método de extensión C #. List.map y filter, de hecho, son funciones de una manera "funcional": obtener una secuencia y af como entrada, devolver una secuencia. Sin tubería, la variante F # será

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

Observe que visualmente se invierte el orden de las funciones (la aplicación aún está en el mismo orden): con |> se ven más "naturales", o mejor, se parecen más a la variante de C #. C # logra el mismo objetivo a través de los métodos de extensión: recuerde que el C # one es en realidad

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

Enumerable.Select es una función en la que el primer parámetro es "this IEnumerable", que el compilador utiliza para transformarlo en a.Select ... Al final, son recursos de lenguaje (se realizan a través de las transformaciones de un compilador en C #, y el uso de operadores y la aplicación parcial en F #) para hacer que las llamadas de funciones anidadas se parezcan más a una cadena de transformaciones.


Bueno, una cosa con la que probablemente se encontrará es el problema con la inferencia de tipos. Mira este ejemplo, por ejemplo:

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)


En realidad, el operador de tuberías no hace más que intercambiar la función y el argumento, por lo que sé que no hay diferencia entre f1 (f2 3) y 3 |> f2 |> f1 además de que este último es más fácil de leer cuando se está encadenando mucho. .

edición : en realidad se define de la misma manera: let inline (|>) xf = fx .

Supongo que la razón por la que tiende a ver el enfoque List.map más que Linq es porque en OCaml (el predecesor de F #), estos operadores siempre han estado allí, por lo que este estilo de codificación está realmente arraigado en la forma en que piensan los programadores funcionales. Una lista es un concepto muy básico en F #, es ligeramente diferente de un IEnumerable (que está más cerca de un Seq).

Linq es en gran medida un compromiso para llevar estos conceptos de programación funcional a C # y VB. Por lo tanto, están en la plataforma .Net y, por lo tanto, están disponibles, pero en F # son algo redundantes.

También List.map es una operación muy simple, mientras que el enfoque Linq trae todo el marco con una evaluación perezosa, etc. que conlleva algunos gastos generales. Pero no creo que eso suponga una diferencia significativa hasta que realmente lo uses mucho. Escuché en algunas conversaciones que la razón por la cual el compilador de C # no usa Linq más es por esta razón, pero en la vida normal no es probable que lo note.

Así que, en general, haz lo que mejor te parezca, no hay bien ni mal. Personalmente, me gustaría ir con los operadores de la Lista porque son más estándar en F # ''idiomático''.

GJ


La canalización es compatible con la inferencia de tipo de izquierda a derecha de F #. a.GroupBy requiere que ya se sepa que el tipo de a es seq<_> , mientras que a |> Seq.groupBy sí mismo infiere que a seq<_> . La siguiente función:

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

requiere que se escriba una anotación de tipo usando LINQ:

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

A medida que se sienta cómodo con el estilo funcional, encontrará formas de hacer que su código sea más conciso. Por ejemplo, la función anterior se puede acortar a:

let increment = Seq.map ((+) 1)


Otros ya explicaron la mayoría de las diferencias entre los dos estilos. Desde mi punto de vista, lo más importante es la inferencia de tipos (mencionada por Daniel) que funciona mejor con el estilo idiomático de F # basado en la segmentación y funciones como List.map .

Otra diferencia es que cuando se usa el estilo F #, puede ver más fácilmente qué parte del cálculo se evalúa perezosamente, cuando se fuerza la evaluación, etc., porque puede combinar funciones para IEnumerable<_> (llamado Seq ) y funciones para listas o matrices

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

También encuentro al operador |> más práctico desde el punto de vista sintáctico, porque nunca sé cómo sangrar correctamente el código que usa .Foo , especialmente dónde colocar el punto. Por otro lado, |> tiene un estilo de codificación bastante establecido en F #.

En general, recomiendo usar el estilo |> porque es "más estándar". No hay nada de malo en usar el estilo C # en F #, pero puede encontrar que escribir código en un estilo más idiomático facilita el uso de algunos conceptos de programación funcional interesantes que funcionan mejor en F # que en C #.