В Haskell, когда мы используем с let? - PullRequest
54 голосов
/ 26 ноября 2011

В следующем коде последняя фраза, которую я могу поставить in перед. Это что-нибудь изменит?

Еще один вопрос: если я решу поставить in перед последней фразой, нужно ли сделать отступ?

Я пробовал без отступов и обнимает жалуется

Последний генератор в do {...} должен быть выражением

import Data.Char
groupsOf _ [] = []
groupsOf n xs = 
    take n xs : groupsOf n ( tail xs )

problem_8 x = maximum . map product . groupsOf 5 $ x
main = do t <- readFile "p8.log" 
          let digits = map digitToInt $concat $ lines t
          print $ problem_8 digits

Редактировать

Хорошо, поэтому люди, похоже, не понимают, о чем я говорю. Позвольте мне перефразировать: следующие два одинаковы, учитывая контекст выше?

1

let digits = map digitToInt $concat $ lines t
print $ problem_8 digits

2

let digits = map digitToInt $concat $ lines t
in print $ problem_8 digits

Еще один вопрос, касающийся объема привязок, объявленных в let: я читаю здесь , что:

where Статьи.

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

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

Обратите внимание, что этого нельзя сделать с помощью выражения let, которое охватывает только выражение , которое оно включает .

Мой вопрос: так, переменные цифры не должны быть видны до последней печатной фразы. Я что-то здесь скучаю?

Ответы [ 4 ]

112 голосов
/ 26 ноября 2011

Краткий ответ : используйте let без in в теле do-блока и в части после | в понимании списка. В другом месте используйте let ... in ....


Ключевое слово let используется в Haskell тремя способами.

  1. Первая форма - это let-выражение .

    let variable = expression in expression
    

    Это можно использовать везде, где разрешено выражение, например,

    > (let x = 2 in x*2) + 3
    7
    
  2. Вторым является let-оператор . Эта форма используется только внутри do-нотации и не использует in.

    do statements
       let variable = expression
       statements
    
  3. Третий аналогичен номеру 2 и используется в списках. Опять нет in.

    > [(x, y) | x <- [1..3], let y = 2*x]
    [(1,2),(2,4),(3,6)]
    

    Эта форма связывает переменную, которая находится в области видимости в последующих генераторах и в выражении перед |.


Причина вашей путаницы в том, что выражения (правильного типа) можно использовать как операторы внутри do-блока, а let .. in .. - это просто выражение.

Из-за правил отступа в haskell строка с более глубоким отступом, чем предыдущая, означает, что это продолжение предыдущей строки, поэтому это

do let x = 42 in
     foo

анализируется как

do (let x = 42 in foo)

Без отступа вы получаете ошибку разбора:

do (let x = 42 in)
   foo

В заключение, никогда не используйте in в понимании списка или do-block. Это бесполезно и запутанно, поскольку эти конструкции уже имеют свою собственную форму let.

18 голосов
/ 26 ноября 2011

Прежде всего, почему обнимает? Haskell Platform - это обычно рекомендуемый способ для новичков, который поставляется с GHC.

Теперь перейдем к ключевому слову let.Простейшая форма этого ключевого слова означает, что всегда может использоваться с in.

let {assignments} in {expression}

Например,

let two = 2; three = 3 in two * three

{assignments} являются только в области действия в соответствующем {expression}. Применяются обычные правила компоновки, означающие, что in должен иметь отступ по крайней мере столько же, сколько let, которому он соответствует, и любойподвыражения, относящиеся к выражению let, также должны иметь отступ по крайней мере столько же.На самом деле это не на 100% верно, но это хорошее правило;Правила компоновки Haskell - это то, к чему вы привыкнете со временем, когда будете читать и писать код на Haskell.Просто помните, что количество отступов - это основной способ указать, какой код относится к какому выражению.

Haskell предоставляет два удобных случая, когда вам не нужно писать in: делать обозначения и составлять списки (на самом деле, понимания монад).Область назначений для этих удобных случаев предопределена.

do foo
   let {assignments}
   bar
   baz

Для обозначения do, {assignments} находятся в области действия для любых операторов, которые следуют, в данном случае, bar и baz, но не foo.Это похоже на то, как если бы мы написали

do foo
   let {assignments}
   in do bar
         baz

Список понятий (или, на самом деле, любое понимание монады) в десагарской нотации, поэтому они предоставляют аналогичные возможности.

[ baz | foo, let {assignments}, bar ]

{assignments} находятся в области видимости для выражений bar и baz, но не для foo.


where несколько отличается.Если я не ошибаюсь, область действия where совпадает с определением определенной функции.Поэтому

someFunc x y | guard1 = blah1
             | guard2 = blah2
  where {assignments}

{assignments} в этом предложении where имеют доступ к x и y.guard1, guard2, blah1 и blah2 все имеют доступ к {assignments} этого предложения where.Как упомянуто в уроке, который вы связали, это может быть полезно, если несколько охранников используют одни и те же выражения.

7 голосов
/ 26 ноября 2011

В нотации do вы действительно можете использовать let с in и без него.Чтобы это было эквивалентно (в вашем случае я позже покажу пример, где вам нужно добавить второй do и, следовательно, больше отступов), вам нужно сделать отступ, как вы обнаружили (если вы используете макет - есливы используете явные скобки и точки с запятой, они в точности эквивалентны).

Чтобы понять , почему это эквивалентно, вы должны фактически получить монады (по крайней мере, до некоторой степени) и посмотреть на обесценениеправила для do обозначений.В частности, такой код:

do let x = ...
   stmts -- the rest of the do block

переводится как let x = ... in do { stmts }.В вашем случае stmts = print (problem_8 digits).Оценка всей десугарированной let привязки приводит к действию ввода-вывода (от print $ ...).И здесь вам нужно понимание монад, чтобы интуитивно согласиться с тем, что нет никакой разницы между do нотациями и «обычными» языковыми элементами, описывающими вычисления, приводящие к монадическим значениям.

Что касается обоих, то почему это возможно:1020 * имеет широкий спектр приложений (большинство из которых не имеют никакого отношения, в частности, к монадам) и имеет длинную историю загрузки.let без in для записи do, с другой стороны, кажется ничем иным, как небольшим кусочком синтаксического сахара.Преимущество очевидно: вы можете связать результаты чистых (не монадических) вычислений с именем, не прибегая к бессмысленным val <- return $ ... и не разбивая блок do на две части:

do stuff
   let val = ...
    in do more
          stuff $ using val

Причина, по которой вам не нужен дополнительный блок do для того, что следует за let, заключается в том, что вы получили только одну строку.Помните, что do e - это e.

Относительно вашего редактирования: digit видимость в следующей строке - это весь смысл.И здесь нет исключений.do нотация становится одним выражением, а let прекрасно работает в одном выражении.where требуется только для вещей, которые не являются выражениями.

Для демонстрации я покажу версию вашего блока do, которая не соответствует действительности.Если вы еще не слишком знакомы с монадами (что-то, что вы должны скоро изменить, ИМХО), игнорируйте оператор >>= и сосредоточьтесь на let.Также обратите внимание, что отступ больше не имеет значения.

main = readFile "p8.log" >>= (\t ->
  let digits = map digitToInt $ concat $ lines t
  in print (problem_8 digits))
1 голос
/ 26 ноября 2011

Некоторые заметки для начинающих о том, что «следуют за двумя одинаковыми».

Например, add1 - это функция, которая добавляет 1 к числу:

add1 :: Int -> Int
add1 x =
    let inc = 1
    in x + inc

Итак, это какadd1 x = x + inc с заменой inc на 1 из let ключевого слова.

Когда вы пытаетесь подавить in ключевое слово

add1 :: Int -> Int
add1 x =
    let inc = 1
    x + inc

, у вас есть ошибка разбора.

Из документации :

Within do-blocks or list comprehensions 
let { d1 ; ... ; dn } 
without `in` serves to introduce local bindings. 

Кстати, есть хорошее объяснение со множеством примеров того, что на самом деле делают ключевые слова where и in.

...