In Haskell, quando usiamo con let?

Nel seguente codice, l’ultima frase che posso mettere davanti. Cambierà qualcosa?

Un’altra domanda: se decido di inserirla in fronte all’ultima frase, devo indentarla?

Ho provato senza indentazione e abbracci lamentele

L’ultimo generatore in do {…} deve essere un’espressione

 import Data.Char groupsOf _ [] = [] groupsOf n xs = take n xs : groupsOf n ( tail xs ) problem_8 x = maximum . map product . groupsOf 5 $ x main = do t <- readFile "p8.log" let digits = map digitToInt $concat $ lines t print $ problem_8 digits 

modificare

Ok, quindi la gente non sembra capire quello che sto dicendo. Lasciatemi riformulare: i due seguenti sono gli stessi, visto il contesto sopra?

1.

 let digits = map digitToInt $concat $ lines t print $ problem_8 digits 

2.

 let digits = map digitToInt $concat $ lines t in print $ problem_8 digits 

Un’altra domanda riguardante la portata delle associazioni dichiarate in let : leggo qui che:

where clausole.

A volte è conveniente eseguire il binding di più equazioni controllate, che richiede una clausola where:

 fxy | y>z = ... | y==z = ... | y<z = ... where z = x*x 

Notare che questo non può essere fatto con un’espressione let, che circoscrive solo l’espressione che racchiude .

La mia domanda: quindi, le cifre variabili non dovrebbero essere visibili per l’ultima frase di stampa. Mi manca qualcosa qui?

Risposta breve : usa let senza in nel corpo di un do-block, e nella parte dopo il | in una lista di comprensione Ovunque, usa let ... in ...


La parola chiave let viene utilizzata in tre modi in Haskell.

  1. La prima forma è una let-espressione .

     let variable = expression in expression 

    Questo può essere utilizzato ovunque sia consentita un’espressione, ad es

     > (let x = 2 in x*2) + 3 7 
  2. Il secondo è un’affermazione . Questo modulo viene utilizzato solo all’interno della do notazione e non viene utilizzato in .

     do statements let variable = expression statements 
  3. Il terzo è simile al numero 2 e viene usato all’interno delle liste di comprensione. Di nuovo, no.

     > [(x, y) | x <- [1..3], let y = 2*x] [(1,2),(2,4),(3,6)] 

    Questo modulo associa una variabile che è nello scope nei generatori successivi e nell'espressione prima del | .


Il motivo della tua confusione qui è che le espressioni (del tipo corretto) possono essere usate come istruzioni all'interno di un blocco di do e let .. in .. sia solo un'espressione.

A causa delle regole di indentazione di haskell, una linea rientrata oltre la precedente significa che è una continuazione della linea precedente, quindi questo

 do let x = 42 in foo 

viene analizzato come

 do (let x = 42 in foo) 

Senza indentazione, si ottiene un errore di analisi:

 do (let x = 42 in) foo 

In conclusione, non usare mai in una list comprehension o in un do-block. È superfluo e confuso, poiché questi costrutti hanno già una loro forma di let .

Prima di tutto, perché abbracci? La piattaforma Haskell è generalmente il modo consigliato per i principianti, che viene fornito con GHC.

Ora poi, sulla parola chiave let . La forma più semplice di questa parola chiave è destinata a essere sempre utilizzata con in .

 let {assignments} in {expression} 

Per esempio,

 let two = 2; three = 3 in two * three 

I {assignments} sono solo nell’ambito della corrispondente {expression} . Si applicano regole di layout regolari, nel senso che in deve essere rientrato almeno quanto il let cui corrisponde, e qualsiasi sub-espressione relativa all’espressione let deve essere ugualmente rientrata almeno tanto. Questo non è vero al 100%, ma è una buona regola empirica; Le regole di layout di Haskell sono qualcosa a cui ti abituerai nel tempo mentre leggi e scrivi il codice Haskell. Basta tenere a mente che la quantità di indentazione è il modo principale per indicare quale codice si riferisce a quale espressione.

Haskell fornisce due casi di convenienza in cui non è necessario scrivere: notazione e comprensione delle liste (in realtà, comprensione di monade). Lo scopo delle assegnazioni per questi casi convenienti è predefinito.

 do foo let {assignments} bar baz 

Per la notazione, i {assignments} sono nel campo di applicazione per qualsiasi affermazione successiva, in questo caso, bar e baz , ma non foo . È come se avessimo scritto

 do foo let {assignments} in do bar baz 

Comprensione delle liste (o, in realtà, qualsiasi comprensione di una monade) in una notazione, quindi forniscono una struttura simile.

 [ baz | foo, let {assignments}, bar ] 

I {assignments} rientrano nella bar espressioni e in baz , ma non per foo .


where è un po ‘diverso. Se non sbaglio, lo scopo di where allinea con una particolare definizione di funzione. Così

 someFunc xy | guard1 = blah1 | guard2 = blah2 where {assignments} 

i {assignments} in questa clausola where hanno accesso a x e y . guard1 , guard2 , blah1 e blah2 hanno tutti accesso alla {assignments} di questa clausola where. Come menzionato nel tutorial che hai collegato, questo può essere utile se più guardie riutilizzano le stesse espressioni.

In notazione, puoi effettivamente usare let con e senza in . Perché sia ​​equivalente (nel tuo caso mostrerò in seguito un esempio in cui devi aggiungere un secondo do e quindi più indentazione), devi indentarlo come hai scoperto (se stai usando il layout – se usi parentesi graffe e punto e virgola, sono esattamente equivalenti).

Per capire perché è equivalente, devi in ​​effetti fare i conti con le monadi (almeno fino a un certo punto) e osservare le regole di desugaring per la notazione. In particolare, codice come questo:

 do let x = ... stmts -- the rest of the do block 

è tradotto in let x = ... in do { stmts } . Nel tuo caso, stmts = print (problem_8 digits) . La valutazione dell’intero desugared let risultati vincolanti in un’azione IO (dalla print $ ... ). E qui, è necessario comprendere le monadi per accettare intuitivamente che non c’è differenza tra do notazioni e gli elementi del linguaggio “regolari” che descrivono un calcolo che ha come risultato valori monadici.

Per quanto riguarda entrambi, perché sono possibili: Beh, let ... in ... ha una vasta gamma di applicazioni (la maggior parte delle quali non ha nulla a che fare con le monadi in particolare), e una lunga storia da avviare. let senza in per la notazione, d’altra parte, sembra essere nient’altro che un piccolo pezzo di zucchero sintattico. Il vantaggio è ovvio: è ansible associare i risultati di calcoli puri (come in, non monadici) a un nome senza ricorrere a una valenza inutile val <- return $ ... e senza dividere il blocco do in due:

 do stuff let val = ... in do more stuff $ using val 

Il motivo per cui non hai bisogno di un blocco di do extra per quanto segue è che hai una sola linea. Ricorda, do e è e .

Per quanto riguarda la tua modifica: la digit visibile nella riga successiva è l'intero punto. E non c'è eccezione per questo o altro. do notazione diventa una singola espressione e let funzionare perfettamente in una singola espressione. where è necessario solo per cose che non sono espressioni.

Per motivi di dimostrazione, mostrerò la versione desugared del blocco do . Se non hai ancora familiarità con le monadi (qualcosa che dovresti cambiare presto IMHO), ignora l'operatore >>= e concentrati sul let . Si noti inoltre che il rientro non ha più importanza.

 main = readFile "p8.log" >>= (\t -> let digits = map digitToInt $ concat $ lines t in print (problem_8 digits)) 

Alcune note per principianti su “stanno seguendo due uguali”.

Ad esempio, add1 è una funzione che aggiunge 1 al numero:

 add1 :: Int -> Int add1 x = let inc = 1 in x + inc 

Quindi, è come add1 x = x + inc con sostituzione inc di 1 da let parola chiave.

Quando si tenta di sopprimere in parola chiave

 add1 :: Int -> Int add1 x = let inc = 1 x + inc 

hai un errore di analisi.

Dalla documentazione :

 Within do-blocks or list comprehensions let { d1 ; ... ; dn } without `in` serves to introduce local bindings. 

A proposito, ci sono delle belle spiegazioni con molti esempi su cosa fanno effettivamente e in parola chiave.