2015-10-12 8 views

risposta

11

Abbiamo bisogno di obiettivi reificati perché il sistema di tipo Haskell è predicativo. Non conosco i dettagli tecnici di esattamente che cosa significa, ma vieta tipi come

[Lens s t a b] 

Per alcuni scopi, è accettabile utilizzare

Functor f => [(a -> f b) -> s -> f t] 

invece, ma quando si arriva in quella, non si ottiene un Lens; si ottiene un LensLike specializzato per alcuni funtori o un altro. I nuovi tipi di ReifiedBlah ti permettono di aggrapparti al polimorfismo completo.

Operativamente, [ReifiedLens s t a b] è un elenco delle funzioni ciascuna delle quali prende un dizionario Functor f, mentre forall f . Functor f => [LensLike f s t a b] è una funzione che prende un dizionario Functor f e restituisce una lista.

Per quanto riguarda ciò che "reify" significa, beh, il dizionario dirà qualcosa, e questo sembra tradursi in una varietà piuttosto sorprendente di significati specifici in Haskell. Quindi nessun commento su questo.

5

Il problema è che, in Haskell, digitare l'astrazione e l'applicazione sono completamente implicite; il compilatore dovrebbe inserirli dove necessario. Vari tentativi di progettare estensioni 'impredicative', in cui il compilatore avrebbe fatto congetture intelligenti su dove metterli, hanno fallito; quindi la cosa più sicura finisce per affidarsi alle regole di Haskell 98:

  • Le astrazioni di tipo si verificano solo al livello più alto di una definizione di funzione.
  • Le applicazioni di tipo si verificano immediatamente ogni volta che una variabile con un tipo polimorfico viene utilizzata in un'espressione.

Quindi se definisco una semplice lente: [1]

lensHead f [] = pure [] 
lensHead f (x:xn) = (:xn) <$> f x 

e usarlo in un'espressione:

[lensHead] 

lensHead ottiene automaticamente applicato ad un insieme di parametri di tipo ; a quel punto non è più un obiettivo, perché non è più polimorfico nel funtore. Il take-away è: un'espressione sempre ha un tipo monomorfico; quindi non è un obiettivo. (Noterai che le funzioni lens accettano argomenti di tipo Getter e Setter, che sono tipi monomorfici, per ragioni analoghe a questo, ma uno [Getter s a] non è un elenco di obiettivi, perché sono stati specializzati per solo getter .)

Che cosa significa reify? La definizione del dizionario è 'rendere reale'. "Reificare" è usato in filosofia per riferirsi all'atto di considerare o trattare qualcosa come reale (piuttosto che ideale o astratto). Nella programmazione, tende a riferirsi a prendere qualcosa che normalmente non può essere trattato come una struttura di dati e rappresentarlo come uno.Ad esempio, in Lisps molto vecchi, non c'erano funzioni di prima classe; invece, dovevi usare S-Expressions per passare 'funzioni' in giro, e eval quando hai bisogno di chiamare la funzione. Le S-Expressions rappresentavano le funzioni in un modo che potevi manipolare nel programma, che viene indicato come reificazione.

In Haskell, in genere non abbiamo bisogno di strategie di reificazione così elaborate come Lisp S-Expressions, in parte perché il linguaggio è progettato per evitare di averne bisogno; ma poiché

newtype ReifiedLens s t a b = ReifiedLens (Lens s t a b) 

ha lo stesso effetto di assumere un valore polimorfica e trasformandolo in un vero valore di prima classe, è indicato come reificazione.

Perché funziona, se le espressioni hanno sempre tipi monomorfici? Beh, perché l'estensione Rank2Types aggiunge una terza regola:

  • Tipo astrazioni si verificano in alto a livello degli argomenti di alcune funzioni, con i cosiddetti tipi rango 2.

ReifiedLens è una funzione di grado 2; così quando si dice

ReifiedLens l 

si ottiene un tipo di lambda intorno l'argomento ReifiedLens, e poi l viene applicata immediatamente al l'argomento di tipo lambda-bound. Quindi l è in effetti solo eta-espanso. (I compilatori sono liberi di eta-ridurre questo e basta usare direttamente l).

Quindi, quando si dice

f (ReifiedLens l) = ... 

sul lato destro, l è una variabile di tipo polimorfo, così ogni uso di l è immediatamente implicitamente assegnato a qualunque argomenti tipo sono necessari per l'espressione di tipo-controllare. Quindi tutto funziona come ti aspetti.

L'altro modo di pensare è che, se si dice

newtype ReifiedLens s t a b = ReifiedLens { unReify :: Lens s t a b } 

le due funzioni ReifiedLens e unReify agire come operatori di astrazione e di applicazioni di tipo esplicito; ciò consente al compilatore di identificare dove si desidera che le astrazioni e le applicazioni si svolgano abbastanza bene da non creare problemi con i sistemi di tipo impredicativo.

[1] Nella terminologia lens, questo è apparentemente chiamato qualcosa di diverso da un 'obiettivo'; tutta la mia conoscenza degli obiettivi viene dalla presentazione di SPJ su di loro quindi non ho modo di verificarlo. Il punto rimane, dal momento che il polimorfismo è ancora necessario per farlo funzionare sia come getter che come setter.

+0

Amo questa spiegazione; Vorrei che la tua risposta non avesse perso l'iniziale onda di votazione di nuove domande. La parte riguardante l'applicazione immediata del tipo lambda sembra essere correlata alla ragione per cui i tipi di rango più elevato sono più facili da gestire rispetto all'impredicatività generale. Un piccolo cavillo: credo che "lensHead" non sia considerato un obiettivo nella terminologia "lens", ma piuttosto un attraversamento, poiché richiede "Applicativo f" e non solo "Functor f". – dfeuer