Хаскелл: Де проти Нехай


117

Я новачок у Haskell, і мене дуже збентежило Where vs. Let . Вони, схоже, забезпечують подібну мету. Я прочитав кілька порівнянь між Де vs. Нехай , але у мене виникають проблеми , коли Discerning використовувати кожен. Може хтось, будь ласка, надати якийсь контекст чи, можливо, кілька прикладів, які демонструють, коли потрібно використовувати один над іншим?

Де проти Нехай

whereРозділ може бути визначений тільки на рівні визначення функції. Зазвичай це ідентично області letвизначення. Єдина відмінність полягає в тому, коли використовуються охоронці . Обсяг цього whereпункту поширюється на всіх охоронців. На відміну від цього, сфера letвиразу - це лише поточне положення функції та захист, якщо вони є.

Haskell шпаргалка

Haskell Wiki дуже докладно і надає різні випадки , але він використовує гіпотетичні приклади. Я вважаю його пояснення занадто короткими для початківця.

Переваги Let :

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

Control.Monad.State

не буде працювати, тому що де посилається на узор, що відповідає f =, де жоден х не знаходиться в області застосування. Навпаки, якби ви почали з дозволу, то у вас не виникло б проблем.

Haskell Wiki про переваги Let

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

Переваги Де :

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)

Декларація проти вираження

Вікі Haskell згадує про те , що де становище є декларативним , а Нехай вираз виразність. Як вони відрізняються від стилю?

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. У першому прикладі, чому " Let" в області, а де " ні"?
  2. Чи можна застосувати Де до першого прикладу?
  3. Чи можуть деякі застосувати це до реальних прикладів, коли змінні представляють фактичні вирази?
  4. Чи є загальне правило, яке слід дотримуватися, коли використовувати кожен?

Оновлення

Для тих, хто пізніше піде за цією ниткою, я знайшов тут найкраще пояснення: " Ніжне введення в Хаскелл ".

Нехай вирази.

Дозволені вирази Haskell корисні, коли потрібен вкладений набір прив’язок. Як простий приклад розглянемо:

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

Набір зв'язків, створених висловом let, взаємно рекурсивний, а прив'язки малюнків трактуються як ледачі візерунки (тобто вони несуть неявний ~). Єдиний вид дозволених декларацій - це підписи типів, прив'язки функцій та прив'язки шаблонів.

Де Клаузи.

Іноді зручно застосовувати прив'язки до кількох захищених рівнянь, для чого потрібне застереження де:

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

Зауважте, що це неможливо зробити з виразом let, який охоплює лише той вираз, який він додає. Де застереження дозволено лише на верхньому рівні набору рівнянь або виразів випадок. Ті ж властивості та обмеження щодо прив’язок у виразах дозволених застосовуються до тих, де пункти. Ці дві форми вкладених областей здаються дуже схожими, але пам’ятайте, що вираз let - це вираз, тоді як, де застереження - ні, це частина синтаксису функцій оголошень та виразів випадок.


9
Мене спантеличила різниця між тим, letі whereколи я вперше почав вивчати Haskell. Я думаю, що найкращий спосіб зрозуміти це - усвідомити, що різниця між ними є дуже малою, і тому нічого турбуватися. Сенс, що whereподається в термінах letчерез дуже просте механічне перетворення. Дивіться haskell.org/onlinereport/decls.html#sect4.4.3.2 Ця трансформація існує лише для нотаційної зручності.
Том Елліс

Зазвичай я використовую те чи інше, залежно від того, що я хочу визначити спочатку. Наприклад, люди часто використовують функції, а потім визначають їх, де. Нехай використовується, якщо хочеться якась імперативна функція.
PyRulez

@Том Елліс, Томе, я намагався зрозуміти посилання, на яке ти посилаєшся, але це було занадто важко для мене, ти можеш пояснити це просте перетворення простим смертним?
jhegedus

1
@jhegedus: f = body where x = xbody; y = ybody ...означаєf = let x = xbody; y = ybody ... in body
Том Елліс

Дякую Томе! Чи може піти навпаки? Чи можна case .... of ... whereякось перетворити вираз на долю в вираз? Я не впевнений у цьому.
jhegedus

Відповіді:


39

1: Проблема в прикладі

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

є параметром x. Речі в whereпункті можуть посилатися лише на параметри функції f(таких немає) та речі у зовнішніх областях.

2: Щоб використовувати a whereв першому прикладі, ви можете ввести другу функцію з ім'ям, яка приймає xпараметр, наприклад, такий:

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

або так:

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

3: Ось повний приклад без ...'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: Коли використовувати letабо whereє питанням смаку. Я використовую letдля того, щоб підкреслити обчислення (перемістивши його вперед) і whereпідкреслити потік програми (переміщення обчислень назад).


2
"Речі, де пункт може посилатися лише на параметри функції f (таких немає) та речі у зовнішніх областях." - Це справді допомагає уточнити це для мене.

28

Незважаючи на те, що існує технічна різниця щодо охоронців, на які вказував ефеміент, існує також концептуальна різниця в тому, чи хочете ви поставити головну формулу наперед із додатковими змінними, визначеними нижче ( where), чи ви хочете визначити все наперед та поставити формулу внизу ( let). Кожен стиль має різний наголос, і ви бачите, що вони використовуються в математичних роботах, підручниках тощо. Взагалі, змінні, які є досить неінтуїтивними, що формула не має сенсу без них, слід визначити вище; змінні, які є інтуїтивними через контекст або їх назви, слід визначити нижче. Наприклад, у прикладі ephemient hasVowel, значення vowelsочевидне, тому його не потрібно визначати вище його використання (ігнорування факту, який letби не працював через охорону).


1
Це забезпечує хороше правило. Не могли б ви детальніше розібратися, чому, здається, сфера дії відрізняється від де?

Тому що так говорить синтаксис Haskell. Вибачте, не маю гарної відповіді. Можливо, найкращі визначення важко читати, коли їх розміщують під "нехай", тому це було заборонено.
gdj

13

Юридичні:

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

Не є законним:

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

Юридичні:

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

Не є законним: (на відміну від ML)

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

13
Чи можете ви пояснити, чому наступні приклади дійсні, а інший - ні?

2
Для тих, хто не знає, це законно: hasVowel = let^M vowels = "AEIOUaeiou"^M in ...( ^Mце новий рядок)
Томас Едінг,

5

Цей приклад з LYHFGG вважаю корисним:

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

let- це вираз, тому ви можете помістити let будь-де (!), де вирази можуть переходити.

Іншими словами, в наведеному вище прикладі це НЕ можливо використовувати whereпросто замінити let(без можливе використання деяких більш багатослівним caseвираження в поєднанні з where).


3

На жаль, більшість відповідей тут занадто технічні для початківця.

Про LHYFGG є відповідна глава з цього приводу, яку ви повинні прочитати, якщо ви ще цього не зробили, але по суті:

  • whereце лише синтаксична конструкція (не цукор ), яка корисна лише для визначення функцій .
  • let ... inє самим виразом , тому ви можете використовувати їх там, де ви можете поставити вираз. Будучи самим виразом, його не можна використовувати для прив'язки речей для охоронців.

Нарешті, ви можете також використовувати letв розумінні списку:

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

Ми включаємо впущення всередині списку так само, як і предикат, тільки він не фільтрує список, він прив'язує лише до імен. Імена, визначені в пусті всередині розуміння списку, є видимими для функції виводу (частина перед |) і всі предикати та розділи, що надходять після прив'язки. Таким чином, ми могли б змусити нашу функцію повертати лише ІМТ людей> = 25:


Це єдина відповідь, яка насправді допомогла мені зрозуміти різницю. Хоча технічні відповіді можуть бути корисні для більш досвідченого Haskell-er, це достатньо просто для початківця, як я! +1
Zac G
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.