Haskell: Где vs. пусть

118

Я новичок в Haskell, и меня очень смущает Where vs. Let . У них обоих, похоже, схожая цель. Я прочитал несколько сравнений между Where и Let, но мне трудно понять, когда использовать каждый. Может ли кто-нибудь предоставить некоторый контекст или, возможно, несколько примеров, демонстрирующих, когда использовать одно вместо другого?

Где vs. пусть

Предложение whereможет быть определено только на уровне определения функции. Обычно это совпадает с объемом letопределения. Единственная разница в том, когда используются охранники . Сфера действия whereстатьи распространяется на всех охранников. Напротив, объем letвыражения - это только текущее предложение функции и защита, если таковая имеется.

Памятка по Haskell

Haskell Wiki очень подробно и предоставляет различные случаи , но он использует гипотетические примеры. Я нахожу его объяснения слишком краткими для новичка.

Преимущества Let :

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

Control.Monad.State

не будет работать, потому что где относится к сопоставлению с образцом f =, где x не входит в область видимости. Напротив, если бы вы начали с let, у вас не было бы проблем.

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 упоминается, что предложение Where является декларативным, а выражение Let - выразительным. Помимо стиля, чем они отличаются?

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. Можно ли применить Where к первому примеру?
  3. Можно ли применить это к реальным примерам, где переменные представляют собой фактические выражения?
  4. Есть ли какое-то общее практическое правило, когда использовать каждый из них?

Обновить

Для тех, кто позже зайдет в эту ветку, я нашел лучшее объяснение здесь: « Нежное введение в Haskell ».

Позвольте выражениям.

Выражения let в Haskell полезны всякий раз, когда требуется вложенный набор привязок. В качестве простого примера рассмотрим:

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

Набор привязок, созданных выражением let, является взаимно рекурсивным, а привязки шаблонов рассматриваются как ленивые шаблоны (т. Е. Они несут неявный ~). Единственный разрешенный вид объявлений - это сигнатуры типов, привязки функций и привязки шаблонов.

Где статьи.

Иногда бывает удобно использовать привязки к нескольким защищенным уравнениям, для чего требуется предложение where:

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

Обратите внимание, что этого нельзя сделать с выражением let, которое охватывает только выражение, которое оно включает. Предложение where допускается только на верхнем уровне набора уравнений или выражения case. Те же свойства и ограничения для привязок в выражениях let применяются к тем, что в предложениях where. Эти две формы вложенной области видимости кажутся очень похожими, но помните, что выражение let - это выражение, а предложение where - нет - это часть синтаксиса объявлений функций и выражений case.

nbro
источник
9
Я был озадачен разница между letи , whereкогда я впервые начал изучать Haskell. Я думаю, что лучший способ понять это - понять, что между ними очень мало различий, и поэтому не о чем беспокоиться. Значение whereдано в терминах letочень простого механического преобразования. См. Haskell.org/onlinereport/decls.html#sect4.4.3.2 На самом деле это преобразование существует только для удобства записи.
Tom Ellis
Я обычно использую один или другой в зависимости от того, что я хочу определить в первую очередь. Например, люди часто используют функции, а затем определяют их где. Let используется, если нужно что-то вроде функции императивного вида.
PyRulez
@Tom Ellis, Том, я пытался понять ссылку, на которую вы ссылаетесь, но для меня это было слишком сложно, не могли бы вы объяснить это простое преобразование простым смертным?
jhegedus
1
@jhegedus: f = body where x = xbody; y = ybody ...означаетf = let x = xbody; y = ybody ... in body
Том Эллис
Спасибо, Том! Может ли быть наоборот? Можно ли как-то преобразовать выражение let в case .... of ... whereвыражение? Я не уверен в этом.
jhegedus

Ответы:

39

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

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

это параметр x. Вещи в whereпредложении могут относиться только к параметрам функции f(их нет) и вещам во внешних областях.

2: Чтобы использовать whereв первом примере, вы можете ввести вторую именованную функцию, которая принимает в xкачестве параметра, например:

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

или вот так:

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

3: Вот полный пример без ...символов:

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подчеркивая поток программы (перемещая вычисления на задний план).

antonakos
источник
2
«Вещи в предложении where могут относиться только к параметрам функции f (их нет) и вещам во внешних областях». - Это действительно помогает мне прояснить ситуацию.
28

Хотя существует техническая разница в отношении охранников, на которые указал эфемиент, существует также концептуальная разница в том, хотите ли вы поставить основную формулу заранее с дополнительными переменными, определенными ниже ( where), или хотите ли вы заранее определить все и поставить формулу ниже ( let). У каждого стиля свой акцент, и вы видите, что оба они используются в математических работах, учебниках и т. Д. Как правило, переменные, которые достаточно неинтуитивны, чтобы формула не имела смысла без них, должны быть определены выше; переменные, которые интуитивно понятны из-за контекста или их имен, должны быть определены ниже. Например, в примере hasVowel от ephemient значение слова vowelsочевидно, поэтому его не нужно определять выше, чем его использование (без учета того факта, что letэто не сработает из-за защиты).

GDJ
источник
1
Это дает хорошее практическое правило. Не могли бы вы уточнить, почему let имеет область видимости иначе, чем where?
Потому что так говорит синтаксис Haskell. Извините, у меня нет хорошего ответа. Может быть, определения с верхним охватом трудно читать, если они помещены под «let», поэтому оно было запрещено.
gdj 06
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 = ...
ephemient
источник
13
Можете ли вы объяснить, почему следующие примеры верны, а другие нет?
2
Для тех, кто не знает, это законно: hasVowel = let^M vowels = "AEIOUaeiou"^M in ...( ^Mэто новая строка)
Thomas Eding
5

Я нашел этот пример из LYHFGG полезным:

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

let- это выражение, поэтому вы можете поместить let любое (!) место, куда могут идти выражения.

Другими словами, в приведенном выше примере это не возможно использовать whereпросто заменить let(без возможно использование некоторых более многословным caseвыражения в сочетании с where).

jhegedus
источник
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

Мы включаем let в понимание списка так же, как и предикат, только он не фильтрует список, а только привязывается к именам. Имена, определенные в let внутри понимания списка, видны функции вывода (часть перед |) и всем предикатам и разделам, которые идут после привязки. Таким образом, мы могли бы заставить нашу функцию возвращать только ИМТ людей> = 25:

Бора М. Альпер
источник
Это единственный ответ, который действительно помог мне понять разницу. Хотя технические ответы могут быть полезны для более опытного специалиста по хаскеллингу, этого достаточно для такого новичка, как я! +1
Zac G