Haskell: Wo gegen Let

117

Ich bin neu in Haskell und ich bin sehr verwirrt von Where vs. Let . Sie scheinen beide einen ähnlichen Zweck zu erfüllen. Ich habe einige Vergleiche zwischen Where vs. Let gelesen, aber ich habe Probleme zu erkennen, wann ich sie verwenden soll. Könnte jemand bitte einen Kontext oder vielleicht ein paar Beispiele angeben, die zeigen, wann man sie übereinander verwendet?

Wo gegen lassen

Eine whereKlausel kann nur auf der Ebene einer Funktionsdefinition definiert werden. Normalerweise ist das identisch mit dem letDefinitionsumfang. Der einzige Unterschied besteht darin, wann Wachen verwendet werden . Der Geltungsbereich der whereKlausel erstreckt sich über alle Wachen. Im Gegensatz dazu ist der Umfang eines letAusdrucks nur die aktuelle Funktionsklausel und ggf. der Schutz.

Haskell Spickzettel

Das Haskell-Wiki ist sehr detailliert und bietet verschiedene Fälle, verwendet jedoch hypothetische Beispiele. Ich finde die Erklärungen für einen Anfänger zu kurz.

Vorteile von Let :

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

Control.Monad.State

funktioniert nicht, da sich where auf die Musterübereinstimmung f = bezieht, wobei kein x im Gültigkeitsbereich liegt. Wenn Sie dagegen mit let begonnen hätten, hätten Sie keine Probleme.

Haskell Wiki über die Vorteile von Let

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

Vorteile von Wo :

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)

Erklärung vs. Ausdruck

Das Haskell-Wiki erwähnt, dass die Where- Klausel deklarativ ist, während der Let- Ausdruck expressiv ist. Wie verhalten sie sich neben dem Stil anders?

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. Im ersten Beispiel, warum ist der Bereich Einlassen, aber wo nicht?
  2. Ist es möglich, Wo auf das erste Beispiel anzuwenden ?
  3. Können einige dies auf reale Beispiele anwenden, bei denen die Variablen tatsächliche Ausdrücke darstellen?
  4. Gibt es eine allgemeine Faustregel, die zu befolgen ist, wann sie verwendet werden sollen?

Aktualisieren

Für diejenigen, die später von diesem Thread kommen, fand ich die beste Erklärung hier: " Eine sanfte Einführung in Haskell ".

Lassen Sie Ausdrücke.

Haskells let-Ausdrücke sind immer dann nützlich, wenn ein verschachtelter Satz von Bindungen erforderlich ist. Betrachten Sie als einfaches Beispiel:

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

Der Satz von Bindungen, der durch einen let-Ausdruck erzeugt wird, ist gegenseitig rekursiv, und Musterbindungen werden als faule Muster behandelt (dh sie tragen ein implizites ~). Die einzigen zulässigen Deklarationen sind Typensignaturen, Funktionsbindungen und Musterbindungen.

Where-Klauseln.

Manchmal ist es zweckmäßig, Bindungen über mehrere geschützte Gleichungen hinweg zu erfassen, was eine where-Klausel erfordert:

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

Beachten Sie, dass dies nicht mit einem let-Ausdruck möglich ist, der nur den darin enthaltenen Ausdruck abdeckt. Eine where-Klausel ist nur auf der obersten Ebene eines Satzes von Gleichungen oder eines Fallausdrucks zulässig. Die gleichen Eigenschaften und Einschränkungen für Bindungen in let-Ausdrücken gelten für diejenigen in where-Klauseln. Diese beiden Formen des verschachtelten Bereichs scheinen sehr ähnlich zu sein, aber denken Sie daran, dass ein let-Ausdruck ein Ausdruck ist, während eine where-Klausel dies nicht ist - er ist Teil der Syntax von Funktionsdeklarationen und case-Ausdrücken.

nbro
quelle
9
Ich war verwirrt über den Unterschied zwischen letund whereals ich anfing, Haskell zu lernen. Ich denke, der beste Weg, dies zu verstehen, besteht darin, zu erkennen, dass es kaum einen Unterschied zwischen den beiden gibt und daher kein Grund zur Sorge besteht. Die Bedeutung von wheregegeben letdurch eine sehr einfache mechanische Transformation gegeben. Siehe haskell.org/onlinereport/decls.html#sect4.4.3.2. Diese Transformation dient eigentlich nur zur Vereinfachung der Notation.
Tom Ellis
Normalerweise verwende ich das eine oder andere, je nachdem, was ich zuerst definieren möchte. Beispielsweise verwenden Benutzer häufig Funktionen und definieren sie dann dort, wo. Let wird verwendet, wenn man eine Art imperativ aussehende Funktion haben möchte.
PyRulez
@ Tom Ellis, Tom, ich habe versucht, den Link zu verstehen, auf den Sie sich beziehen, aber es war zu schwierig für mich. Können Sie diese einfache Transformation bloßen Sterblichen erklären?
Jhegedus
1
@jhegedus: f = body where x = xbody; y = ybody ...bedeutetf = let x = xbody; y = ybody ... in body
Tom Ellis
Danke Tom! Kann es umgekehrt gehen? Ist es möglich, einen let- case .... of ... whereAusdruck irgendwie in einen Ausdruck umzuwandeln ? Da bin ich mir nicht sicher.
Jhegedus

Antworten:

39

1: Das Problem im Beispiel

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

ist der Parameter x. Dinge in der whereKlausel können sich nur auf die Parameter der Funktion f(es gibt keine) und Dinge in äußeren Bereichen beziehen .

2: Um a whereim ersten Beispiel zu verwenden, können Sie eine zweite benannte Funktion einführen, die das xals Parameter verwendet, wie folgt:

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

oder so:

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

3: Hier ist ein vollständiges Beispiel ohne die ...'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: Wann zu verwenden letoder whereist Geschmackssache. Ich benutze let, um eine Berechnung hervorzuheben (indem ich sie nach vorne verschiebe) und whereum den Programmablauf hervorzuheben (indem ich die Berechnung nach hinten verschiebe).

Antonakos
quelle
2
"Dinge in der where-Klausel können sich nur auf die Parameter der Funktion f (es gibt keine) und Dinge in äußeren Bereichen beziehen." - Das hilft mir wirklich, es zu klären.
28

Während es den technischen Unterschied in Bezug auf Wachen gibt, auf den Ephemient hingewiesen hat, gibt es auch einen konzeptionellen Unterschied darin, ob Sie die Hauptformel mit den unten definierten zusätzlichen Variablen ( where) vorab stellen möchten oder ob Sie alles vorab definieren und die Formel setzen möchten unten ( let). Jeder Stil hat eine andere Betonung, und Sie sehen, dass beide in Mathepapieren, Lehrbüchern usw. verwendet werden. Im Allgemeinen sollten Variablen, die so unintuitiv sind, dass die Formel ohne sie keinen Sinn ergibt, oben definiert werden. Variablen, die aufgrund des Kontexts oder ihrer Namen intuitiv sind, sollten unten definiert werden. Zum Beispiel ist im hasVowel-Beispiel von ephemient die Bedeutung von vowelsoffensichtlich und muss daher nicht über seiner Verwendung definiert werden (ohne Berücksichtigung der Tatsache, dass letdies aufgrund des Schutzes nicht funktionieren würde).

gdj
quelle
1
Dies bietet eine gute Faustregel. Könnten Sie näher erläutern, warum let einen anderen Umfang hat als wo?
Weil die Haskell-Syntax dies sagt. Entschuldigung, ich habe keine gute Antwort. Vielleicht sind Definitionen mit dem höchsten Anwendungsbereich schwer zu lesen, wenn sie unter einem "Let" versteckt sind, sodass sie nicht zulässig waren.
GDJ
13

Legal:

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

Nicht legal:

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

Legal:

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

Nicht legal: (im Gegensatz zu ML)

let vowels = "AEIOUaeiou"
in hasVowel = ...
kurzlebig
quelle
13
Können Sie erklären, warum die folgenden Beispiele gültig sind, während das andere nicht gültig ist?
2
Für diejenigen, die nicht wissen, ist dies legal: hasVowel = let^M vowels = "AEIOUaeiou"^M in ...( ^Mist Newline)
Thomas Eding
5

Ich fand dieses Beispiel von LYHFGG hilfreich:

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

letist ein Ausdruck, mit dem Sie let überall (!) einfügen können, wohin Ausdrücke gehen können.

Mit anderen Worten, im obigen Beispiel ist es nicht möglich, whereeinfach zu ersetzen let(ohne vielleicht einen ausführlicheren caseAusdruck in Kombination mit zu verwenden where).

jhegedus
quelle
3

Leider sind die meisten Antworten hier für Anfänger zu technisch.

LHYFGG hat ein relevantes Kapitel darüber - das Sie lesen sollten, wenn Sie es noch nicht getan haben, aber im Wesentlichen:

  • whereist nur ein syntaktisches Konstrukt (kein Zucker ), das nur bei Funktionsdefinitionen nützlich ist .
  • let ... inist ein Ausdruck selbst , daher können Sie sie überall dort verwenden, wo Sie einen Ausdruck einfügen können. Als Ausdruck selbst kann er nicht dazu verwendet werden, Dinge für Wachen zu binden.

Schließlich können Sie auch letin Listenverständnissen verwenden:

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

Wir fügen ein Let in ein Listenverständnis ein, ähnlich wie wir es bei einem Prädikat tun würden, nur dass es die Liste nicht filtert, sondern nur an Namen bindet. Die Namen, die in einem Let innerhalb eines Listenverständnisses definiert sind, sind für die Ausgabefunktion (den Teil vor dem |) und alle Prädikate und Abschnitte, die nach der Bindung kommen, sichtbar . Wir könnten also dafür sorgen, dass unsere Funktion nur die BMIs von Personen> = 25 zurückgibt:

Bora M. Alper
quelle
Dies ist die einzige Antwort, die mir tatsächlich geholfen hat, den Unterschied zu verstehen. Während die technischen Antworten für einen erfahrenen Haskeller nützlich sein könnten, ist dies für einen Anfänger wie mich prägnant genug! +1
Zac G