2011-10-10 12 views
13

Digamos que tengo una lista y un conjunto y quiero hacer algunas cosas con ellos:F #: matar redundancia en un mapa/Reducir/Filtro

let outA = inA |> List.map(fun x -> x + 1) |> List.filter(fun x -> x > 10) 
let outB = inB |> Set.map(fun x -> x + 1) |> Set.filter(fun x -> x > 10) 

Ahora, obviamente, que maneja las listas A y B se encarga de conjuntos. Sin embargo, me resulta muy molesto tener que escribir List.List. una y otra vez: no solo es una repetición repetitiva y prolija que no transmite información y obstaculiza la lectura de la funcionalidad del código, sino que también es de facto. escriba la anotación de la que tengo que hacer un seguimiento y mantenerme sincronizado con el resto de mi código.

Lo que yo quiero ser capaz de hacer es algo como lo siguiente:

let outA = inA |> map(fun x -> x + 1) |> filter(fun x -> x > 10) 
let outB = inB |> map(fun x -> x + 1) |> filter(fun x -> x > 10) 

Con el compilador sabiendo que inA es una lista y INB es un conjunto, y por lo tanto todas las operaciones son de la clase correcta y, por lo tanto, outA es una lista y outB es un conjunto. Puedo lograr esto parcialmente con Seq:

let map(x) = 
    Seq.map(x) 

let filter(x) = 
    Seq.filter(x) 

Y puedo escribir exactamente eso. El problema con esto es que convierte todo en Secuencias, y ya no puedo realizar operaciones de lista/configuración en ellas. Del mismo modo,

let outA = inA.Select(fun x -> x + 1).Where(fun x -> x > 10) 
let outB = inB.Select(fun x -> x + 1).Where(fun x -> x > 10) 

También elimina todo eso, pero luego echa todo a IEnumerable. He conseguido que sea muy agradable mediante la conversión de todos los métodos estáticos en los métodos de instancia a través de extensiones:

type Microsoft.FSharp.Collections.List<'a> with 
    member this.map(f) = this |> List.map(f) 
    member this.filter(f) = this |> List.filter(f) 

let b = a.map(fun x -> x + 1).filter(fun x -> x > 10) 

pero sospecho que se ejecutará en los problemas tipo de inferencia que se mencionan aquí: Method Chaining vs |> Pipe Operator? Realmente no lo sé; No estoy familiarizado con el funcionamiento del algoritmo de inferencia tipo.

La conclusión es que creo que voy a estar haciendo muchísimas operaciones de estas listas/set/array map/reducir/filtrar y quiero que se vean tan bonitas y limpias como sea posible. En este momento, además de distraerme de los bits importantes en la expresión (es decir, el "mapa" y la lambda) también proporcionan anotaciones de tipo de facto en un lugar donde el compilador debería saber muy bien de qué colección estoy pasar es.

Además, este es exactamente el tipo de cosa que la herencia OO y el polimorfismo debe resolver, así que me preguntaba si había algún patrón funcional equivalente que no sé que lo resolvería tan elegantemente. Tal vez podría hacer una verificación de tipo en mi propio estático map y realizar la función del tipo relevante map en la entrada?

¿Alguien tiene alguna solución mejor que las que ya he probado, o me estoy enfrentando a una limitación fundamental del motor de inferencia tipo F #, que debería aceptar y seguir?

+2

En mi opinión, la El nombre del módulo hace que el código sea más descriptivo. Usted sabe que es una Lista/Conjunto, simplemente mirando el código. – ebb

+4

Básicamente quieres clases de tipo :) Pagar Haskell, o permanecer con nosotros y utilizar nombres calificados. –

+1

@ebb: Pero el punto de la inferencia de tipo era que no necesitaba describir el código tanto = D. –

Respuesta

11

Esto no es lo que resuelve OO/herencia/polimorfismo. Más bien es la cosa que resuelven las 'clases de tipo' (a la Haskell). El sistema de tipo .NET no es capaz de hacer clases de tipos, y F # no intenta agregar esa capacidad. (Usted puede hacer algunos trucos peludos con inline, pero tiene varias limitaciones.)

Recomiendo aceptarlo y seguir adelante.

+0

Estoy buscando clases de tipos ahora. Pensé que el objetivo de OO/Herencia/Polimorfismo era que pudieras darle un nombre de método, e inmediatamente sabría (basado en lo que pones como "este") * a qué método llamar (de las muchas clases) que todos tienen ese método), que es mi problema. ¿O estoy perdiendo algo por completo? –

+3

En un nivel alto, su descripción es correcta, y parece que OO haría esto. Pero en los detalles, los tipos con subtipificación no funcionan ... Revise las clases de tipos, pero posiblemente vea también "varianza" (covarianza y contravarianza) en lo que se refiere a OO para tener una idea de por qué OO no puede bastante resolver esto en general. – Brian

+2

+1, pero creo que el soporte para las clases de tipos podría hacerse en .NET, es solo que el sistema de tipos F # no implementa esto. – Ingo

5

Estoy de acuerdo con Brian, simplemente te acostumbrarás a esto. Como mencioné en la respuesta a su pregunta anterior, esto a veces es bastante útil, porque verá lo que hace su código (qué parte evalúa perezosamente, qué parte usa arreglos para la eficiencia, etc.)

Además, algunas funciones sólo están disponibles para algunos tipos - por ejemplo hay List.tail o List.foldBack, pero no existen funciones similares para IEnumerable (en el módulo Seq) - por buenas razones, ya que daría lugar a mal código .

En Haskell, clases de tipos puede describir tipos que tienen algunas funciones (como map y filter), pero no creo que escalar muy bien - para especificar el tipo clases para la # biblioteca F, que terminaría con una jerarquía de clases de tipos que especifican estructuras de datos similares a listas, estructuras de datos similares a listas con funciones similares a tail y foldBack y demasiadas otras restricciones.

Además, para muchos trabajos simples de procesamiento de la lista, también se puede utilizar por comprensión que le dan una gran cantidad de sintaxis más agradable (pero, por supuesto, sólo es útil para cosas básicas):

let outB = seq { for x in inA do 
        if x > 10 do yield x + 1 }