Haskell: Where vs. Let


117

Sono nuovo di Haskell e sono molto confuso da Where vs. Let . Entrambi sembrano fornire uno scopo simile. Ho letto alcuni confronti tra Where vs. Let, ma ho difficoltà a discernere quando usarli. Qualcuno potrebbe fornire un contesto o forse alcuni esempi che dimostrano quando utilizzare l'uno sull'altro?

Where vs. Let

Una whereclausola può essere definita solo a livello di definizione di una funzione. Di solito, questo è identico all'ambito della letdefinizione. L'unica differenza è quando vengono utilizzate le guardie . L'ambito della whereclausola si estende a tutte le guardie. Al contrario, l'ambito di letun'espressione è solo la clausola di funzione corrente e la guardia, se presente.

Haskell Cheat Sheet

Il Wiki di Haskell è molto dettagliato e fornisce vari casi ma utilizza esempi ipotetici. Trovo le sue spiegazioni troppo brevi per un principiante.

Vantaggi di Let :

f :: State s a
f = State $ \x -> y
   where y = ... x ...

Control.Monad.State

non funzionerà, perché where si riferisce al pattern matching f =, dove nessuna x è nell'ambito. Al contrario, se avessi iniziato con let, non avresti avuto problemi.

Haskell Wiki sui vantaggi di Let

f :: State s a
f = State $ \x ->
   let y = ... x ...
   in  y

Vantaggi di Where :

f x
  | cond1 x   = a
  | cond2 x   = g a
  | otherwise = f (h x a)
  where
    a = w x

f x
  = let a = w x
    in case () of
        _ | cond1 x   = a
          | cond2 x   = g a
          | otherwise = f (h x a)

Dichiarazione contro espressione

Il wiki di Haskell afferma che la clausola Where è dichiarativa mentre l' espressione Let è espressiva. A parte lo stile, come si comportano diversamente?

Declaration style                     | Expression-style
--------------------------------------+---------------------------------------------
where clause                          | let expression
arguments LHS:     f x = x*x          | Lambda abstraction: f = \x -> x*x
Pattern matching:  f [] = 0           | case expression:    f xs = case xs of [] -> 0
Guards:            f [x] | x>0 = 'a'  | if expression:      f [x] = if x>0 then 'a' else ...
  1. Nel primo esempio, perché l' ambito Let in è ma Where no?
  2. È possibile applicare Where al primo esempio?
  3. Alcuni possono applicare questo ad esempi reali in cui le variabili rappresentano espressioni reali?
  4. Esiste una regola pratica generale da seguire quando utilizzarli?

Aggiornare

Per coloro che vengono da questo thread più avanti ho trovato la migliore spiegazione da trovare qui: " A Gentle Introduction to Haskell ".

Lasciate che le espressioni.

Le espressioni let di Haskell sono utili ogni volta che è richiesto un insieme nidificato di associazioni. Come semplice esempio, considera:

let y   = a*b
    f x = (x+y)/y
in f c + f d

L'insieme di associazioni create da un'espressione let è reciprocamente ricorsiva e le associazioni di pattern sono trattate come pattern pigri (cioè portano un ~ implicito). L'unico tipo di dichiarazioni consentite sono le firme dei tipi, le associazioni di funzioni e le associazioni di modelli.

Dove clausole.

A volte è conveniente definire l'ambito delle associazioni su diverse equazioni protette, che richiedono una clausola where:

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

Notare che ciò non può essere fatto con un'espressione let, che si limita a limitarsi all'espressione che racchiude. Una clausola where è consentita solo al livello più alto di un insieme di equazioni o espressioni di casi. Le stesse proprietà e gli stessi vincoli sui collegamenti nelle espressioni let si applicano a quelli nelle clausole where. Queste due forme di ambito annidato sembrano molto simili, ma ricorda che un'espressione let è un'espressione, mentre una clausola where non lo è: fa parte della sintassi delle dichiarazioni di funzione e delle espressioni case.


9
Ero perplesso dalla differenza tra lete wherequando ho iniziato a imparare Haskell. Penso che il modo migliore per capirlo sia rendersi conto che c'è pochissima differenza tra i due, e quindi niente di cui preoccuparsi. Il significato di wheredato in termini di letattraverso una trasformazione meccanica molto semplice. Vedi haskell.org/onlinereport/decls.html#sect4.4.3.2 Questa trasformazione esiste solo per comodità di notazione, in realtà.
Tom Ellis

Di solito uso l'uno o l'altro a seconda di ciò che voglio definire per primo. Ad esempio, le persone spesso usano le funzioni e poi le definiscono in dove. Let è usato se si vuole una sorta di funzione di sguardo imperativo.
PyRulez

@ Tom Ellis, Tom, stavo cercando di capire il collegamento a cui ti riferisci ma è stato troppo difficile per me, potresti spiegare questa semplice trasformazione ai comuni mortali?
jhegedus

1
@jhegedus: f = body where x = xbody; y = ybody ...significaf = let x = xbody; y = ybody ... in body
Tom Ellis,

Grazie Tom! Può andare il contrario? È possibile trasformare in case .... of ... wherequalche modo un'espressione let in un'espressione? Non sono sicuro di questo.
jhegedus

Risposte:


39

1: il problema nell'esempio

f :: State s a
f = State $ \x -> y
    where y = ... x ...

è il parametro x. Le cose nella whereclausola possono fare riferimento solo ai parametri della funzione f(non ce ne sono) e le cose negli ambiti esterni.

2: Per utilizzare a wherenel primo esempio, puoi introdurre una seconda funzione denominata che accetta xcome parametro, in questo modo:

f = State f'
f' x = y
    where y = ... x ...

o così:

f = State f'
    where
    f' x = y
        where y = ... x ...

3: Ecco un esempio completo senza la ...s:

module StateExample where

data State a s = State (s -> (a, s))

f1 :: State Int (Int, Int)
f1 = State $ \state@(a, b) ->
    let
        hypot = a^2 + b^2
        result = (hypot, state)
    in result

f2 :: State Int (Int, Int)
f2 = State f
    where
    f state@(a, b) = result
        where
        hypot = a^2 + b^2
        result = (hypot, state)

4: Quando usarlo leto whereè una questione di gusti. Uso letper enfatizzare un calcolo (spostandolo in primo piano) e whereper enfatizzare il flusso del programma (spostando il calcolo sul retro).


2
"Le cose nella clausola where possono fare riferimento solo ai parametri della funzione f (non ce ne sono) e le cose negli ambiti esterni." - Questo mi aiuta davvero a chiarirlo.

28

Sebbene ci sia la differenza tecnica rispetto alle guardie che ha sottolineato l'effimero, c'è anche una differenza concettuale nel fatto che si desideri mettere la formula principale in anticipo con variabili extra definite di seguito ( where) o se si desidera definire tutto in anticipo e inserire la formula sotto ( let). Ogni stile ha un'enfasi diversa e si vedono entrambi usati in documenti di matematica, libri di testo, ecc. In generale, le variabili che sono sufficientemente poco intuitive da non avere senso senza di esse dovrebbero essere definite sopra; le variabili intuitive a causa del contesto o dei loro nomi dovrebbero essere definite di seguito. Ad esempio, nell'esempio hasVowel di ephemient, il significato di vowelsè ovvio e quindi non è necessario definirlo al di sopra del suo utilizzo (trascurando il fatto che letnon funzionerebbe a causa della guardia).


1
Ciò fornisce una buona regola pratica. Potresti approfondire il motivo per cui sembra che l'ambito sia diverso da dove?

Perché la sintassi Haskell lo dice. Mi dispiace, non ho una buona risposta. Forse le definizioni top-scoped sono difficili da leggere quando nascoste sotto un "let", quindi non era consentito.
gdj

13

Legale:

main = print (1 + (let i = 10 in 2 * i + 1))

Non legale:

main = print (1 + (2 * i + 1 where i = 10))

Legale:

hasVowel [] = False
hasVowel (x:xs)
  | x `elem` vowels = True
  | otherwise = False
  where vowels = "AEIOUaeiou"

Non legale: (a differenza di ML)

let vowels = "AEIOUaeiou"
in hasVowel = ...

13
Puoi spiegare perché i seguenti esempi sono validi mentre l'altro no?

2
Per coloro che non lo sanno, questo è legale: hasVowel = let^M vowels = "AEIOUaeiou"^M in ...( ^Mè una nuova riga)
Thomas Eding

5

Ho trovato utile questo esempio di LYHFGG :

ghci> 4 * (let a = 9 in a + 1) + 2  
42  

letè un'espressione in modo da poter inserire un let punto qualsiasi (!) in cui le espressioni possono andare.

In altre parole, nell'esempio di cui sopra è non possibile utilizzare wheresostituire semplicemente let(senza magari utilizzando alcuni più dettagliato caseespressione combinata con where).


3

Purtroppo, la maggior parte delle risposte qui sono troppo tecniche per un principiante.

LHYFGG ha un capitolo pertinente su di esso, che dovresti leggere se non lo hai già fatto, ma in sostanza:

  • whereè solo un costrutto sintattico (non uno zucchero ) utile solo per le definizioni di funzioni .
  • let ... inè un'espressione stessa , quindi puoi usarli ovunque tu possa mettere un'espressione. Essendo un'espressione in sé, non può essere usata per legare cose per le guardie.

Infine, puoi usare anche letnelle comprensioni di lista:

calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]
-- w: width
-- h: height

Includiamo un let all'interno di una lista di comprensione molto simile a un predicato, solo che non filtra la lista, si lega solo ai nomi. I nomi definiti in un let all'interno di una lista di comprensione sono visibili alla funzione di output (la parte prima di |) e tutti i predicati e le sezioni che vengono dopo l'associazione. Quindi potremmo fare in modo che la nostra funzione restituisca solo l'IMC di persone> = 25:


Questa è l'unica risposta che effettivamente mi ha aiutato a capire la differenza. Mentre le risposte tecniche potrebbero essere utili per un Haskell-er più esperto, questo è abbastanza succinto per un principiante come me! +1
Zac G,
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.