Haskell: Where vs. Let

117

Jestem nowy w Haskell i jestem bardzo zdezorientowany przez Where vs. Let . Wydaje się, że oba mają podobny cel. Przeczytałem kilka porównań między Where vs. Let, ale mam problem z rozróżnieniem, kiedy użyć każdego z nich. Czy ktoś mógłby podać jakiś kontekst lub może kilka przykładów, które pokazują, kiedy używać jednego nad drugim?

Gdzie vs. Let

wherePunkt może być określony tylko przy poziomie definicji funkcji. Zwykle jest to identyczne z zakresem letdefinicji. Jedyna różnica dotyczy sytuacji, w których używa się strażników . Zakres whereklauzuli obejmuje wszystkich strażników. W przeciwieństwie do tego zakres letwyrażenia to tylko bieżąca klauzula funkcji i ochrona, jeśli taka istnieje.

Ściągawka Haskell

Haskell Wiki jest bardzo szczegółowy i zapewnia różne przypadki, ale używa hipotetycznych przykładów. Uważam, że jego wyjaśnienia są zbyt krótkie dla początkującego.

Zalety Let :

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

Control.Monad.State

nie zadziała, ponieważ gdzie odnosi się do wzorca pasującego do f =, gdzie nie ma x w zasięgu. Z drugiej strony, gdybyś zaczął od let, nie miałbyś problemów.

Haskell Wiki o zaletach Let

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

Zalety miejsca :

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)

Deklaracja a wyrażenie

Wiki Haskell wspomina, że klauzula Where jest deklaratywna, podczas gdy wyrażenie Let jest ekspresyjne. Poza stylem, czym różnią się od siebie?

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. W pierwszym przykładzie, dlaczego zakres Let in but gdzie go nie ma?
  2. Czy można się zgłosić Gdzie do pierwszego przykładu?
  3. Czy ktoś może zastosować to do rzeczywistych przykładów, w których zmienne reprezentują rzeczywiste wyrażenia?
  4. Czy istnieje ogólna zasada, której należy przestrzegać, kiedy używać każdego z nich?

Aktualizacja

Dla tych, którzy przejdą przez ten wątek później, najlepsze wyjaśnienie znalazłem tutaj: „ Delikatne wprowadzenie do Haskella ”.

Niech wyrażenia.

Wyrażenia let Haskella są przydatne, gdy wymagany jest zagnieżdżony zestaw powiązań. Jako prosty przykład rozważ:

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

Zestaw powiązań utworzonych przez wyrażenie let jest wzajemnie rekurencyjny, a powiązania wzorców są traktowane jako leniwe wzorce (tj. Zawierają niejawne ~). Jedynym dozwolonym rodzajem deklaracji są podpisy typów, powiązania funkcji i powiązania wzorców.

Gdzie klauzule.

Czasami wygodnie jest określić zakres powiązań dla kilku chronionych równań, co wymaga klauzuli where:

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

Zauważ, że nie można tego zrobić za pomocą wyrażenia let, które ogranicza się tylko do wyrażenia, które zawiera. Klauzula where jest dozwolona tylko na najwyższym poziomie zestawu równań lub wyrażenia przypadku. Te same właściwości i ograniczenia dotyczące powiązań w wyrażeniach let mają zastosowanie do tych w klauzulach where. Te dwie formy zasięgu zagnieżdżonego wydają się bardzo podobne, ale pamiętaj, że wyrażenie let jest wyrażeniem, podczas gdy klauzula where nie jest - jest częścią składni deklaracji funkcji i wyrażeń wielkości liter.

nbro
źródło
9
Zaskoczyła mnie różnica między leti wherekiedy zacząłem się uczyć Haskella. Myślę, że najlepszym sposobem, aby to zrozumieć, jest uświadomienie sobie, że między nimi jest bardzo niewielka różnica, a zatem nie ma się czym martwić. Znaczenie wherepodane w kategoriach letbardzo prostej mechanicznej transformacji. Zobacz haskell.org/onlinereport/decls.html#sect4.4.3.2 Ta transformacja istnieje tak naprawdę tylko dla wygody notacji.
Tom Ellis
Zwykle używam jednego lub drugiego, w zależności od tego, co chcę najpierw zdefiniować. Na przykład ludzie często używają funkcji, a następnie definiują je, gdzie. Let jest używane, jeśli chce się czegoś w rodzaju funkcji o charakterze imperatywnym.
PyRulez,
@Tom Ellis, Tom, próbowałem zrozumieć łącze, do którego się odnosisz, ale było to dla mnie zbyt trudne, czy mógłbyś wyjaśnić tę prostą transformację zwykłym śmiertelnikom?
jhegedus
1
@jhegedus: f = body where x = xbody; y = ybody ...oznaczaf = let x = xbody; y = ybody ... in body
Tom Ellis
Dziękuję Tom! Czy to może być odwrotne? Czy można case .... of ... wherejakoś przekształcić wyrażenie let w wyrażenie? Nie jestem tego pewien.
jhegedus

Odpowiedzi:

39

1: Problem w przykładzie

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

jest parametrem x. Rzeczy w whereklauzuli mogą odnosić się tylko do parametrów funkcjif (nie ma ich) i rzeczy w zewnętrznych zakresach.

2: Aby użyć a wherew pierwszym przykładzie, możesz wprowadzić drugą nazwaną funkcję, która przyjmuje xparametr jako parametr, na przykład:

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

lub tak:

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

3: Oto pełny przykład bez ...znaków:

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: Kiedy używać letlub wherezależy od gustu. Używam letdo podkreślenia obliczeń (przesuwając je do przodu) i wheredo podkreślenia przepływu programu (przesuwając obliczenia do tyłu).

antonakos
źródło
2
„Rzeczy w klauzuli where mogą odnosić się tylko do parametrów funkcji f (nie ma ich) i rzeczy w zakresach zewnętrznych”. - To naprawdę pomaga mi to wyjaśnić.
28

Chociaż istnieje różnica techniczna w odniesieniu do strażników, na którą zwrócił uwagę Ephemient, istnieje również różnica pojęciowa w tym, czy chcesz umieścić główną formułę z góry z dodatkowymi zmiennymi zdefiniowanymi poniżej ( where), czy też chcesz zdefiniować wszystko z góry i umieścić wzór poniżej ( let). Każdy styl ma inny akcent i widać, że oba są używane w pracach matematycznych, podręcznikach itp. Ogólnie, zmienne, które są na tyle nieintuicyjne, że wzór nie ma sensu bez nich, powinny być zdefiniowane powyżej; zmienne, które są intuicyjne ze względu na kontekst lub ich nazwy, należy zdefiniować poniżej. Na przykład w przykładzie hasVowel firmy Ephemient znaczenie vowelsjest oczywiste, więc nie trzeba go definiować powyżej jego użycia (pomijając fakt, letże nie zadziała ze względu na ochronę).

gdj
źródło
1
Zapewnia to dobrą praktyczną zasadę. Czy mógłbyś wyjaśnić, dlaczego wydaje się, że zakres inny niż gdzie?
Ponieważ tak mówi składnia Haskella. Przepraszam, nie mam dobrej odpowiedzi. Być może definicje o najwyższym zakresie są trudne do odczytania, gdy są umieszczone pod „let”, więc zostało to niedozwolone.
gdj
13

Prawny:

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

Nielegalne:

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

Prawny:

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

Niedopuszczalne: (w przeciwieństwie do ML)

let vowels = "AEIOUaeiou"
in hasVowel = ...
ephemient
źródło
13
Czy możesz wyjaśnić, dlaczego poniższe przykłady są ważne, a inne nie?
2
Dla tych, którzy nie wiedzą, jest to legalne: hasVowel = let^M vowels = "AEIOUaeiou"^M in ...( ^Mto nowa linia)
Thomas Eding
5

Ten przykład z LYHFGG okazał się pomocny:

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

letjest wyrażeniem, więc możesz wstawić let dowolne (!) miejsce, do którego mogą się udać wyrażenia.

Innymi słowy, w powyższym przykładzie nie można wherepo prostu zastąpić let(być może bez użycia bardziej rozwlekłego casewyrażenia w połączeniu z where).

jhegedus
źródło
3

Niestety, większość odpowiedzi tutaj jest zbyt technicznych dla początkującego.

LHYFGG ma na ten temat odpowiedni rozdział - który powinieneś przeczytać, jeśli jeszcze tego nie zrobiłeś, ale w istocie:

  • wherejest tylko konstruktem składniowym (nie cukrem ), który jest przydatny tylko przy definiowaniu funkcji .
  • let ... injest wyrażeniem samym w sobie , więc możesz ich używać wszędzie tam, gdzie możesz umieścić wyrażenie. Jako samo wyrażenie nie może być używane do wiązania rzeczy dla strażników.

Na koniec możesz także używać letw składaniu list:

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

Umieszczamy let wewnątrz wyrażenia listowego, podobnie jak w przypadku predykatu, z tą różnicą, że nie filtruje ono listy, a jedynie wiąże się z nazwami. Nazwy zdefiniowane w let wewnątrz wyrażenia listowego są widoczne dla funkcji wyjściowej (część przed |) oraz dla wszystkich predykatów i sekcji, które pojawiają się po powiązaniu. Moglibyśmy więc sprawić, by nasza funkcja zwracała tylko BMI osób> = 25:

Bora M. Alper
źródło
To jedyna odpowiedź, która pomogła mi zrozumieć różnicę. Chociaż odpowiedzi techniczne mogą być przydatne dla bardziej doświadczonego Haskellera, jest to wystarczająco zwięzłe dla początkującego, takiego jak ja! +1
Zac G