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.


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


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

28

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


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

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


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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.