2011-10-10 14 views
13

Diciamo che ho una lista e un set e voglio fare alcune cose con loro:F #: uccidere la ridondanza in Map/Reduce/Filter

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) 

Ora, ovviamente una maniglia liste e B maniglie set. Tuttavia, trovo molto fastidioso dover scrivere ancora List.List.: non solo è verbose, ripetitivo boilerplate che non trasmette informazioni e interferisce con la lettura della funzionalità del codice, ma è anche di fatto scrivi annotazioni che devo tenere traccia e mantenere sincronizzate con il resto del mio codice.

Quello che voglio essere in grado di fare è qualcosa di simile al seguente:

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 il compilatore sapendo che inA è una lista e inb è un insieme, e, quindi, tutte le operazioni sono dalla classe corretta e quindi outA è una lista e outB è un insieme. Posso parzialmente ottenere questo risultato con Seq:

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

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

E posso scrivere esattamente questo. Il problema con questo è che inserisce tutto in Sequenze e non posso più eseguire operazioni di elenco/set su di essi. Analogamente,

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) 

Rimuove anche tutto ciò, ma quindi trasmette tutto a IEnumerable. Sono riuscito ad ottenere abbastanza bello convertendo tutti i metodi statici in metodi di istanza tramite estensioni:

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) 

ma ho il sospetto che si incorrere in problemi di tipo inferenza qui menzionati: Method Chaining vs |> Pipe Operator? Non lo so davvero; Non ho familiarità con il funzionamento dell'algoritmo di inferenza del tipo.

La linea di fondo è, immagino che sto andando a fare un sacco di queste operazioni lista/serie/matrice mappa/riduci/filtro e voglio renderle più belle e pulite possibile. In questo momento, oltre a distogliermi dai bit importanti dell'espressione (cioè la "mappa" e il lambda), forniscono anche annotazioni di tipo de-facto in un punto in cui il compilatore dovrebbe sapere-jolly-bene quale sia la collezione entrare è

Inoltre, questo è esattamente il tipo di cosa che l'eredità e il polimorfismo OO sono pensati per risolvere, quindi mi chiedevo se ci fosse qualche schema funzionale equivalente che non so che lo risolverebbe altrettanto elegantemente. Forse potrei fare un controllo di tipo nel mio statico map ed eseguire la funzione map del tipo rilevante sull'input?

Qualcuno ha una soluzione migliore di quelle che ho già provato, o sto sbattendo la testa in una limitazione fondamentale del motore di inferenza di tipo F #, che dovrei semplicemente accettare e andare avanti?

+2

A mio parere, la il nome del modulo rende il codice più descrittivo. Sai che è un elenco/set, solo guardando il codice. – ebb

+4

Fondamentalmente vuoi classi di tipi :) Fai clic su Haskell, o rimani con noi e utilizza nomi qualificati. –

+1

@ebb: Ma il punto di inferenza del tipo era che non avevo bisogno di descrivere il codice così tanto = D. –

risposta

11

Questa non è la cosa che OO/eredità/polimorfismo risolve. Piuttosto è la cosa che risolve le "classi tipo" (a la Haskell). Il sistema di tipo .NET non è in grado di eseguire classi di tipi e F # non tenta di aggiungere tale capacità. (Puoi fare alcuni trucchi pelosi con inline, ma ha varie limitazioni.)

Raccomando di accettarlo e andare avanti.

+0

Sto esaminando le classi di tipi ora. Ho pensato che il punto di OO/Inheritance/Polymorphism fosse che potevi dargli un nome di metodo, e avrebbe immediatamente saputo (basato su quello che hai inserito come "questo") * quale * metodo da chiamare (tra le molte classi che hanno tutti quel metodo), che è il mio problema. O mi sto perdendo completamente? –

+3

Ad un livello elevato, la descrizione è corretta e sembra che OO lo farebbe. Ma nei dettagli, i tipi con sottotitoli non funzionano ... Controllate le classi di tipi, ma probabilmente vedete anche "varianza" (covarianza e controvarianza) in relazione a OO per capire perché OO non può piuttosto risolvere questo in generale. – Brian

+2

+1, ma penso che il supporto per i typeclass potrebbe essere fatto su .NET, è solo che il sistema di tipo F # non lo implementa. – Ingo

5

Sono d'accordo con Brian - ci si abitua semplicemente a questo. Come ho detto nella risposta alla tua domanda precedente, questo è a volte abbastanza utile, perché vedrai cosa fa il tuo codice (quale parte valuta pigramente, quale parte usa gli array per l'efficienza, ecc.)

Inoltre, alcune funzioni sono disponibili solo per alcuni tipi - per esempio c'è List.tail o List.foldBack, ma non esistono funzioni simili per IEnumerable (nel modulo Seq) - per buone ragioni in quanto porterebbe a codice cattivo .

In Haskell, classi di tipo può descrivere tipi che hanno alcune funzioni (come map e filter), ma io non credo che scalano molto bene - per specificare le classi di tipo per la libreria F #, si finirebbe con una gerarchia di classi di tipi che specificano strutture di dati di tipo elenco, strutture di dati di tipo elenco con funzioni tail e foldBack -like e troppi altri vincoli.

parte, per molti lavori semplici lista di elaborazione, è anche possibile utilizzare comprehensions che vi danno un sacco di sintassi più bello (ma naturalmente, è utile solo per le cose di base):

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