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
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.
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
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
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.