Si prega di confermare o correggere il mio inglese “interpretazione” di questo Haskell snippet di codice

StackOverflow https://stackoverflow.com/questions/775730

Domanda

Io sono uno sviluppatore C# che lavora attraverso Il "Mondo Reale Haskell" per capire veramente la programmazione funzionale, in modo che quando vengo a sapere di F#, io davvero grok e non solo "scrivere codice C# F#", per così dire.

Bene, oggi mi sono imbattuto in un esempio che ho pensato che ho capito 3 diverse volte, solo per vedere poi qualcosa ho perso, aggiornare la mia interpretazione, e recurse (e maledizione, credetemi).

Ora credo che mi fanno realmente capire, e ho scritto una dettagliata inglese "interpretazione" di seguito.Si può Haskell guru si prega di confermare che la comprensione, o punto di capire cosa mi sono perso?

Nota:Il Haskell snippet di codice (citato direttamente dal libro) è la definizione di un tipo personalizzato che è destinato a essere isomorfi al costruito in Haskell tipo di elenco.

Il Haskell snippet di codice

data List a = Cons a (List a)
              | Nil
              defining Show

EDIT:Dopo alcune risposte, vedo un equivoco che ho fatto, ma non sono abbastanza chiaro sulla Haskell "analisi" delle norme che avrebbero dovuto correggere l'errore.Così ho incluso il mio originale (errata) interpretazione qui sotto, seguito da una correzione, seguita dalla domanda che rimane ancora poco chiaro per me.

EDIT:Qui è il mio originale (errata) dell'inglese "interpretazione" del frammento

  1. Io sono la definizione di un tipo denominato "Elenco".
  2. Il tipo di Lista è stato parametrizzato.Ha un solo parametro di tipo.
  3. Ci sono 2 valore costruttori che può essere usato per fare le istanze di Elenco.Un valore costruttore viene chiamato "Zero" e l'altro valore costruttore viene chiamato "Contro".
  4. Se si utilizza il "Nil" costruttore di valori, quindi non ci sono campi.
  5. Il "Contro" valore costruttore ha un solo parametro di tipo.
  6. Se si utilizza il "Contro" costruttore di valori, ci sono 2 campi che devono essere fornite.Il primo campo obbligatorio è un esempio di Elenco.Il secondo campo obbligatorio è un'istanza di una.
  7. (Ho volutamente omesso nulla di "definizione Mostra" perché non fa parte di ciò che voglio soffermarmi, per ora).

La corretta interpretazione sarebbe come segue (le modifiche sono in GRASSETTO)

  1. Io sono la definizione di un tipo denominato "Elenco".
  2. Il tipo di Lista è stato parametrizzato.Si ha un solo parametro di tipo.
  3. Ci sono 2 valore costruttori che può essere usato per fare le istanze di Elenco.Un valore costruttore è chiamato "Zero" e l'altro valore costruttore viene chiamato "Contro".
  4. Se si utilizza il "Nil" costruttore di valori, quindi non ci sono campi.

    5.(questa linea è stata eliminata ...non è preciso) Il "Contro" valore costruttore ha un solo parametro di tipo.

  5. Se si utilizza il "Contro" costruttore di valori, ci sono 2 campi che devono essere fornite.Il primo campo obbligatorio è un'istanza di una.Il secondo è un campo obbligatorio istanza di "Lista-di-un".

  6. (Ho volutamente omesso nulla di "definizione Mostra" perché non fa parte di ciò che voglio soffermarmi, per ora).

La questione che non è ancora chiaro

La confusione iniziale è stata per quanto riguarda la parte del frammento che legge "Contro un (Elenco a)".Infatti, quella è la parte che non è ancora chiaro per me.

Le persone hanno fatto notare che ogni elemento della riga dopo il "Contro" è un token tipo, non un valore.In modo che significa questa riga dice "Il Contro valore costruttore dispone di 2 campi:uno di tipo 'a' e l'altro di tipo "list-of-a'."

Che è molto utile sapere.Tuttavia, qualcosa non è ancora chiaro.Quando vado a creare istanze utilizzando il Contro valore costruttore, quelle istanze di "interpretare" la prima " a " come significato "inserire il valore passato qui." Ma lo fanno non interpretare il secondo 'un' allo stesso modo.

Per esempio, consideriamo questo GHCI sessione:

*Main> Cons 0 Nil
Cons 0 Nil
*Main> Cons 1 it
Cons 1 (Cons 0 Nil)
*Main> 

Quando scrivo "Contro 0 Nil", utilizza la "Contro" valore costruttore per creare un'istanza dell'Elenco.Da 0, si apprende che il parametro " tipo "Integer".Finora, nessuna confusione.

Tuttavia, è anche determina che il valore il primo campo del Cons è 0.Eppure si determina nulla circa il valore il secondo campo ...si determina solo che il secondo campo è un tipo della Lista "Integer".

Quindi la mia domanda è, perché non è "un" nel primo campo significa "il tipo di questo campo è 'un' e il valore di questo campo è 'un'", mentre "un" nel secondo campo significa solo "il tipo di questo campo è 'Elenco di un'"?

EDIT:Credo di aver visto la luce, grazie per le risposte.Mi permetta di articolare qui.(E se in qualche modo è ancora errato, in qualche modo, per favore, con tutti i mezzi fatemi sapere!)

Nel frammento di "Cons (Elenco a)", stiamo dicendo che il "Contro" valore costruttore ha due campi, e che il primo campo è di tipo 'a', e che il secondo campo è di tipo 'List'.

Che è quello che stiamo dicendo! In particolare, stiamo dicendo NULLA circa i valori!Questo è un punto chiave che mi mancava.

Più tardi, vogliamo creare un'istanza, utilizzando il "Contro" costruttore di valori.Abbiamo tipo di questo interprete:"Contro 0 Nil".Questo in modo esplicito dice il Contro valore costruttore di utilizzare 0 per il valore del primo campo, e di utilizzare Nil come valore per il secondo campo.

E che è tutto là è ad esso.Una volta che si sa che il valore di definizione del costruttore specifica nulla, ma i tipi di, tutto diventa chiaro.

Grazie a tutti per le utili risposte.E, come ho detto, se qualcosa è rimasto fuori, per favore, con tutti i mezzi che mi dica su di esso.Grazie.

È stato utile?

Soluzione

  • Il "Contro" valore costruttore ha un solo parametro di tipo.

Nope:hai già parametrizzato quando hai dichiarato data List a.Una efficace struttura di questo è che se ho un Nil ::Elenco Int, io non interscambio con un Nullo ::Elenco Char.

  • Se si utilizza il "Contro" costruttore di valori, ci sono 2 campi che devono essere fornite.Il primo campo obbligatorio è un esempio di Elenco.Il secondo campo obbligatorio è un'istanza di una.

Hai scambiato:il primo campo obbligatorio è un'istanza di una, il secondo campo è un esempio di Elenco.

In questo capitolo del Mondo Reale Haskell possono essere di interesse.

Grazie.Che è il capitolo che ho su adesso.Quindi ...quando il codice dice "Contro un (Elenco a)", ho pensato che il "Contro" una parte di che dichiarava che il Contro valore costruttore è stato parametrizzato.Non sono ancora coperto la sintassi per parametrizzare i tipi, così ho intuito che la sintassi deve richiedere la ri-affermando "a" se si intende utilizzare.Ma tu stai dicendo che non è necessario?E che, pertanto, non è quello che "a" significa?

Nope.Una volta dichiarare un parametro all'interno del nostro tipo, abbiamo la possibilità di riutilizzare l'altro modo di dire "che tipo deve essere utilizzato." E 'un po' come una a -> b -> a firma del tipo:una è la parametrizzazione del tipo, ma poi devo usare lo stesso il valore di ritorno.

OK, ma questo è fonte di confusione.Sembra che la prima "a" significa "il primo campo è un'istanza di un",

Nope, che è non vero.Significa solo che il tipo di dati parametrizes più di un qualche tipo.

e significa ANCHE "il primo campo ha lo stesso valore il valore passato per una".In altre parole, consente di specificare il tipo E il valore.

No, non è vero.

Ecco un esempio istruttivo, la sintassi di cui si può o non può avere mai visto prima:

foo :: Num a => a -> a

Questo è un abbastanza standard e la firma per una funzione che prende un numero e fa qualcosa e ti dà un altro numero.Quello che in realtà significa "numero" in Haskell-parlare, però, è arbitrario di tipo "a" che implementa il "Num" classe".

Quindi, questo analizza in inglese:

Lascia un indicare un tipo di attuazione Num typeclass, la firma di questo metodo è un parametro con il tipo a e il valore di ritorno di tipo a

Qualcosa di simile accade con i dati.

Essa si verifica anche a me che l'istanza di Elenco nel disciplinare di Contro è anche confuso, è:essere molto attenti quando l'analisi che:mentre il Contro è la specifica di un costruttore, che è fondamentalmente un modello che Haskell sta per avvolgere i dati, (Elenco a) appare come un costruttore, ma in realtà è semplicemente un tipo, come Int o Double.una è un tipo, NON un valore in ogni senso del termine.

Edit: In risposta all'ultima modifica.

Penso che una dissezione è chiamato prima per.Poi farò I conti con le vostre domande punto per punto.

Haskell dati i costruttori sono un po ' strano, perché definire firma del costruttore, e non è necessario apportare altre impalcature.Tipi di dati in Haskell non hanno alcuna nozione di variabile membro.(Nota:c'è una sintassi alternativa che questo modo di pensare è più assoggettabile a, ma lasciamo perdere per ora).

Un'altra cosa è che Haskell codice è denso;il suo tipo di firma sono simili.Quindi aspettiamo di vedere lo stesso simbolo riutilizzati in contesti diversi.Tipo di interferenza, inoltre, gioca un ruolo importante qui.

Così, di nuovo al vostro tipo:

data List a = Cons a (List a)
              | Nil

Ho questo pezzo in vari pezzi:

data Elenco

Questo definisce il nome del tipo, e qualsiasi tipi con parametri che si avranno in seguito.Si noti che sarà solo vedere questo spettacolo fino in altro tipo di firma.

Contro a (List a) |
Nil

Questo è il nome del costruttore. Questo NON È un tipo di.Possiamo, tuttavia, pattern match per esso, ala:

foo :: List a -> Bool
foo Nil = True

Si noti come Lista a è il tipo nella firma, e a Zero sia i dati del costruttore e la "cosa" ci pattern match per.

Cons a (Elenco a)

Questi sono i tipi di valori slot del costruttore.Contro ha due voci, una è di tipo a e uno di tipo Elenco.

Quindi la mia domanda è, perché non è "un" nel primo campo significa "il tipo di questo campo è 'a' e il valore di questo campo è 'un'", mentre "un" nel secondo campo significa solo "il tipo di questo campo è 'Elenco di un'"?

Semplice:non pensare ad esso come noi, specificando il tipo;pensate ha Haskell è un'interferenza di tipo fuori di esso.Così, per i nostri intenti e scopi, stiamo semplicemente attaccare un 0, un Nil nella seconda sezione.Quindi, Haskell, guarda il nostro codice e pensa:

  • Hmm, mi chiedo che tipo di Svantaggi 0 a Zero
  • Bene, Contro un costruttore per un Elenco.Mi chiedo che cosa il tipo di Lista è un
  • Beh, una è utilizzato nel primo parametro, quindi, dal momento che il primo parametro è un Int (un altro semplificazione;0 è in realtà una cosa strana che è typeclassed come Num), il che significa che a è un Num
  • Ehi, bene, il che significa anche che il tipo di Nil Elenco Int, anche se non c'è niente che sarebbe effettivamente dire che

(Nota, questo non è in realtà come è implementato.Haskell può fare un sacco di cose strane, mentre la deduzione tipi, che è in parte perché i messaggi di errore succhiare.)

Altri suggerimenti

Le analogie sono solitamente carenti in tutti i modi, ma poiché si sa C # Ho pensato che questo potrebbe essere utile.

Questo è come vorrei descrivere la definizione List a in C #, forse questo cancella alcune cose su (o, più probabilmente, si confonde ancora di più).

class List<A>
{
}

class Nil<A> : List<A>
{
    public Nil() {}
}

class Cons<A> : List<A>
{
    public A Head;
    public List<A> Tail;

    public Cons(A head, List<A> tail)
    {
        this.Head = head;
        this.Tail = tail;
    }
}

Come si può vedere;

  • il tipo List ha un parametro unico tipo (<A>),
  • il costruttore Nil non ha alcun parametro,
  • e il costruttore Cons ha due parametri, un valore di tipo head A e un valore di tipo tail List<A>.

Ora, in Haskell il Nil e Cons sono solo costruttori per il tipo di dati List a, in C # sono anche tipi di per sé, così che è dove l'analogia non riesce.

Ma spero che questo ti dà qualche senso intuitivo di ciò che i diversi A di rappresentare.

(E per favore commento su come questo orribile paragone non rende giustizia ai tipi di dati di Haskell.)

Cons a (List a)

Il primo campo di un Contro è un valore di tipo "a". Il secondo è un valore di tipo "List a", cioè un elenco parametrizzato con lo stesso tipo del parametro della lista corrente.

5 è sbagliato, e direi 6 come segue per sostituire entrambi:

Contro {1} a {2} (Elenco a) {3} è un costruttore chiamato Contro (parte prima del {1}) per un valore di tipo List un (elenco dati una parte) che necessita di due valori: uno di tipo a (la parte tra {1} e {2}) ed uno di tipo List una (la parte tra {2} e {3}).

Per aiutarvi con una fonte di confusione apparente: in Haskell quasi mai necessario dare parametri di tipo esplicite - inferenza deduce i tipi dai vostri valori. Quindi, in un certo senso, sì, quando si passa a un valore a una funzione o costruttore, è inoltre possibile specificare un tipo, vale a dire. il tipo del valore passato-in.

Sì la sintassi dei dati è un po 'confusa, perché puns nomi ei tipi e non ha realmente un sintattica distinzione tra di loro. In particolare, in una definizione costruttore come:

Cons a (List a)

La prima parola è il nome del costruttore; ogni altra parola è il nome di un certo tipo predichiarati. Così sia a e List a sono già in ambito (la a è stato portato a portata dal a in "data List a"), e si sta dicendo che questi sono i tipi dei parametri. Il loro ruolo potrebbe essere meglio dimostrata affermando la stessa cosa usando record di sintassi :

Cons { headL :: a, tailL :: List a }

vale a dire. un valore di tipo List Int, se è stato costruito con il costruttore Cons, ha due campi: un Int e List Int. Se è stato costruito con Nil, non ha campi.

  

Quando digito "Cons 0 Nil", utilizza il costruttore valore "Cons" per creare un'istanza di List. Da 0, si viene a sapere che il parametro di tipo è "Integer". Finora, nessuna confusione.

     

Tuttavia, esso determina, inoltre, che il valore del primo campo dei Cons è 0. Eppure determina nulla circa il valore del secondo campo ... si determina solo che il secondo campo ha un tipo di "Lista Integer" .

No, determina che il valore del secondo campo è Nil. Data la tua definizione, Nil è un valore di tipo List a. Pertanto è così Cons 0 Nil. E nel Cons 1 it il valore del secondo campo è it; cioè, Cons 0 Nil. Questo è esattamente ciò che mostra la REPL: Cons 1 (Cons 0 Nil).

Ho guardato la tua domanda modificato.

  

Quando creo istanze utilizzando il Cons   valore costruttore, tali istanze   "Interpretare" la prima 'a' nel senso   "Inserire il valore passato qui.

In "Contro un (elenco A)", sia "a" e "lista A" sono i tipi. Non capisco che cosa "valore" deve con esso.

  

Quando digito "Contro 0 Nil", utilizza il   "Contro" il valore di costruzione per creare un   istanza di List. Da 0, impara   che il parametro tipo è "Integer".   Finora, nessuna confusione.

     

Tuttavia, determina anche che il   valore del primo campo delle Contro   è 0. Eppure determina nulla   il valore del secondo campo ... è   determina solo che il secondo campo   ha un tipo di "Lista Integer".

Il valore del secondo campo è Nil.

  

Quindi la mia domanda è: perché lo fa "a" nella   primo campo significa "il tipo di questo   campo è 'a' e il valore di questo   campo è 'un'", mentre 'a' nella seconda   campo significa soltanto "il tipo di questo   campo è 'Elenco di un' "?

"a" nel primo campo significa "il tipo di questo campo è 'un'". "Elenco A" nel secondo campo significa "il tipo di questo campo è 'List un'". Nel caso di "Contro 0 Nil" sopra, 'un' è dedotto per essere "intero". Quindi, "Contro un (elenco A)" diventa "Contro Integer (Lista Integer)". Il 0 è un valore di tipo intero. Il Nil è un valore di tipo "Lista Integer".

  

il valore di questo campo è 'un'

Non capisco cosa intendi per questo. 'A' è una variabile di tipo; cosa ha a che fare con i valori?

Solo per dare qualche "aiuto" in più, nel caso in cui si sta ancora guardando questa discussione. Haskell ha un paio di convenzioni idee di quel casino fino altrui come le cose dovrebbero essere fatte - in Haskell, un tipo parametrico è così comunemente accettato, che di solito è pensato come una funzione di tipo di livello. Analogamente, i costruttori valore sono pensati come funzioni "speciali", che consentono anche pattern matching, oltre alla loro "assumere un valore (o più) e produrre un valore come risultato".

Un'altra caratteristica "divertente" di Haskell è che non lo fa esplicitamente (o implicitamente) di valutare gli argomenti a una funzione, anche se questo argomento è tra parentesi . Permettetemi di affermare che in modo leggermente diverso: le funzioni di Haskell non valutano gli argomenti tra parentesi prima di altri argomenti. Gli argomenti sono messi tra parentesi a fini di raggruppamento solo, non averli valutati "prima". Haskell assegna argomenti ( "applica") una funzione ad una precedenza maggiore rispetto a qualsiasi altra operazione - anche superiore un'applicazione funzione implicita di uno dei suoi argomenti. Questo è il motivo per cui il costruttore ha Cons parentesi intorno al secondo argomento, (List a) - per dire al compilatore che Cons ha due argomenti, e non tre . Le parentesi sono per raggruppare solo, e non per la precedenza!

Come un po 'un argomento lato, fare attenzione con i tipi di F #. Dal momento che F # ha le sue radici nella ML, i suoi tipi parametrizzati avere i parametri di fronte - int list, non (List Int) sul retro! Haskell fa il contrario, perché è lo stesso modo in cui lo fa Haskell funzioni - prima della funzione, poi gli argomenti alla funzione. Questo incoraggia un modello di uso comune, e spiega perché i tipi di Haskell e costruttori di valore sono capitalizzati -. Ricordare che hai a che fare con un tipo / cosa correlata alla classe

Va bene, ho finito; grazie per avermi permesso di mettere questo enorme muro O' Testo sulla vostra proprietà ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top