Haskell: Где против Let

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

Где vs. Let

Предложение

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

Haskell Cheat Sheet

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

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

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

Control.Monad.State

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

Haskell Wiki о преимуществах Let

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

Преимущества 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)

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

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

Update

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

Пусть выражения.

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, которое ограничивается только выражением, которое оно охватывает. Предложение where разрешено только на верхнем уровне набора уравнений или выражения case. Те же свойства и ограничения на привязки в выражениях let применяются к тем, где содержатся предложения. Эти две формы вложенной области кажутся очень схожими, но помните, что выражение let является выражением, тогда как предложение where не является - оно является частью синтаксиса объявлений функций и выражений case.

Ответ 1

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 $ \[email protected](a, b) ->
    let
        hypot = a^2 + b^2
        result = (hypot, state)
    in result

f2 :: State Int (Int, Int)
f2 = State f
    where
    f [email protected](a, b) = result
        where
        hypot = a^2 + b^2
        result = (hypot, state)

4: Когда использовать let или where, это вопрос вкуса. Я использую let, чтобы подчеркнуть вычисление (перемещая его вперед) и where, чтобы подчеркнуть поток программы (перемещая вычисление на спину).

Ответ 2

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

Ответ 3

Допустимо:

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 = ...

Ответ 4

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

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

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

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

Ответ 5

К сожалению, большинство ответов здесь слишком технически для новичка.

У LHYFGG есть соответствующая глава -which, которую вы должны прочитать, если вы еще этого не сделали, но по сути:

  • 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: