Haskell: Typeclass vs passaggio di una funzione


16

A me sembra che puoi sempre passare argomenti di funzione piuttosto che usare una typeclass. Ad esempio, anziché definire la tabella dei tipi di uguaglianza:

class Eq a where 
  (==)                  :: a -> a -> Bool

E usarlo in altre funzioni per indicare l'argomento type deve essere un'istanza di Eq:

elem                    :: (Eq a) => a -> [a] -> Bool

Non possiamo semplicemente definire la nostra elemfunzione senza usare una typeclass e invece passare un argomento di funzione che fa il lavoro?


2
questo si chiama dizionario passando. Puoi considerare i vincoli della typeclass come argomenti impliciti.
Poscat,

2
Potresti farlo, ma ovviamente è molto più conveniente non dover passare una funzione e utilizzarla solo "standard" a seconda del tipo.
Robin Zigmond,

2
Potresti metterlo così, sì. Ma direi che c'è almeno un altro importante vantaggio: la capacità di scrivere funzioni polimorfiche che funzionano su qualsiasi tipo che implementa una particolare "interfaccia" o insieme di caratteristiche. Penso che i vincoli della typeclass lo esprimano molto chiaramente in un modo in cui non passa argomenti extra per le funzioni. In particolare a causa delle (purtroppo implicite) "leggi" che molte macchine da scrivere devono soddisfare. Un Monad mvincolo mi dice di più che passare ulteriori argomenti di tipi a -> m ae funzioni m a -> (a -> m b) -> m b.
Robin Zigmond,


1
L' TypeApplicationsestensione consente di rendere esplicito l'argomento implicito. (==) @Int 3 5confronta 3e in 5particolare come Intvalori. Puoi pensare @Intcome una chiave nel dizionario delle funzioni di uguaglianza specifiche del tipo, piuttosto che la Intfunzione di confronto specifica stessa.
Chepner,

Risposte:


19

Sì. Questo si chiama "dizionario che passa stile". A volte, quando faccio alcune cose particolarmente complicate, ho bisogno di scartare una macchina da scrivere e trasformarla in un dizionario, perché il passaggio del dizionario è più potente 1 , ma spesso piuttosto ingombrante, rendendo il codice concettualmente semplice molto complicato. Uso lo stile del passaggio del dizionario a volte in lingue che non sono Haskell per simulare le macchine da scrivere (ma ho imparato che di solito non è un'idea grandiosa come sembra).

Naturalmente, ogni volta che c'è una differenza nel potere espressivo, c'è un compromesso. Mentre puoi usare una determinata API in più modi se è scritta usando DPS, l'API ottiene più informazioni se non puoi. Un modo in cui questo appare in pratica è in Data.Set, che si basa sul fatto che esiste un solo Orddizionario per tipo. I Setnegozi i suoi elementi ordinati in base al Ord, e se si crea un set con un dizionario, e poi inserito un elemento con uno diverso, come sarebbe possibile con DPS, si potrebbe rompere Set's invariante e causare il crash. Questo problema di unicità può essere mitigato usando un fantasma esistenziale tipo per segnare il dizionario, ma, ancora una volta, al costo di un po 'di fastidiosa complessità nell'API. Questo si presenta anche più o meno allo stesso modo Typeablenell'API.

L'unicità non emerge molto spesso. Ciò che la macchina da scrivere è eccezionale è scrivere codice per te. Per esempio,

catProcs :: (i -> Maybe String) -> (i -> Maybe String) -> (i -> Maybe String)
catProcs f g = f <> g

che accetta due "processori" che accettano un input e potrebbero fornire un output e li concatena, appiattendoli Nothing, dovrebbero essere scritti in DPS in questo modo:

catProcs f g = (<>) (funcSemi (maybeSemi listSemi)) f g

In sostanza, abbiamo dovuto precisare il tipo in cui lo stiamo usando di nuovo, anche se lo abbiamo già scritto nella firma del tipo, e anche questo era ridondante perché il compilatore conosce già tutti i tipi. Poiché esiste solo un modo per costruire un dato Semigroupin un tipo, il compilatore può farlo per te. Questo ha un effetto di tipo "interesse composto" quando inizi a definire molte istanze parametriche e ad usare la struttura dei tuoi tipi per calcolare per te, come nei Data.Functor.*combinatori, e viene usato con grande effetto con deriving viacui puoi essenzialmente ottenere tutto struttura algebrica "standard" del tuo tipo scritta per te.

E non farmi nemmeno iniziare su MPTC e fundeps, che restituiscono informazioni al controllo dei caratteri e all'inferenza. Non ho mai provato a convertire una cosa del genere in DPS - sospetto che comporterebbe il passaggio di molte prove di uguaglianza di tipo - ma in ogni caso sono sicuro che sarebbe molto più lavoro per il mio cervello di quanto non mi sentirei a mio agio con.

-

1 U lla si utilizza reflectionin questo caso diventano equivalente al potere - ma reflectionpuò anche essere ingombrante per l'uso.



Sono molto interessato ai fondi espressi tramite DPS. Conosci alcune risorse raccomandabili su questo argomento? Comunque, spiegazione molto comprensibile.
bob

@bob, non per caso, ma sarebbe un'esplorazione interessante. Forse fai una nuova domanda al riguardo?
luqui,

5

Sì. Questo (chiamato passaggio del dizionario) è fondamentalmente ciò che fa il compilatore per le macchine da scrivere. Per quella funzione, eseguita letteralmente, sarebbe un po 'così:

elemBy :: (a -> a -> Bool) -> a -> [a] -> Bool
elemBy _ _ [] = False
elemBy eq x (y:ys) = eq x y || elemBy eq x ys

La chiamata elemBy (==) x xsè ora equivalente a elem x xs. E in questo caso specifico, puoi fare un passo ulteriore: eqogni volta ha lo stesso primo argomento, quindi puoi fare in modo che il chiamante abbia la responsabilità di applicarlo, e finire con questo:

elemBy2 :: (a -> Bool) -> [a] -> Bool
elemBy2 _ [] = False
elemBy2 eqx (y:ys) = eqx y || elemBy2 eqx ys

La chiamata elemBy2 (x ==) xsè ora equivalente a elem x xs.

... Oh aspetta. Questo è solo any. (E in effetti, nella libreria standard,.elem = any . (==) )


Il passaggio del dizionario AFAIU è l'approccio di Scala alla codifica delle classi di tipi. Questi argomenti extra possono essere dichiarati come implicite il compilatore li inietterebbe per te dall'ambito.
michid
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.