В Haskell, когда мы используем это с let?
В следующем коде последней фразой я могу поставить 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, которое распространяется только на выражение , которое оно включает.
Мой вопрос: поэтому переменные цифры не должны быть видны последней фразе. Я что-то пропустил здесь?
Ответы
Ответ 1
Короткий ответ. Используйте let
без in
в теле блока do и в части после |
в понимании списка. В любом месте используйте let ... in ...
.
Ключевое слово let
используется тремя способами в Haskell.
-
Первая форма - это let-expression.
let variable = expression in expression
Это может использоваться везде, где разрешено выражение, например
> (let x = 2 in x*2) + 3
7
-
Второй - это оператор let-statement. Эта форма используется только внутри do-notation и не использует in
.
do statements
let variable = expression
statements
-
Третий похож на номер 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. Это ненужно и запутанно, так как те конструкции уже имеют свою собственную форму let
.
Ответ 2
Во-первых, почему объятия? платформа Haskell, как правило, рекомендуется использовать для новичков, которые поставляются с GHC.
Теперь выберите ключевое слово let
. Простейшая форма этого ключевого слова всегда используется с in
.
let {assignments} in {expression}
Например,
let two = 2; three = 3 in two * three
{assignments}
находятся только в области видимости в соответствующем {expression}
. применяются обычные правила компоновки, что означает, что in
должен иметь отступ, по меньшей мере, столько же, сколько let
, которое соответствует to, и любые подвыражения, относящиеся к выражению 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
Перечислите понимание (или действительно, любое понимание монады) desugar в обозначение, чтобы они предоставили аналогичный объект.
[ baz | foo, let {assignments}, bar ]
{assignments}
находятся в области выражений bar
и baz
, но не для foo
.
where
несколько отличается. Если я не ошибаюсь, область where
выстраивается в линию с определением определенной функции. Так
someFunc x y | guard1 = blah1
| guard2 = blah2
where {assignments}
the {assignments}
в этом предложении where
имеют доступ к x
и y
. guard1
, guard2
, blah1
и blah2
все имеют доступ к {assignments}
этого предложения where
. Как упоминается в учебнике, которое вы связали, это может быть полезно, если несколько охранников повторно используют одни и те же выражения.
Ответ 3
В обозначении 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
приводит к действию IO (от print $ ...
). И здесь вам нужно понять монады, чтобы интуитивно согласиться с тем, что нет разницы между обозначениями do
и "регулярными" языковыми элементами, описывающими вычисление, приводящее к монадическим значениям.
Как для обоих, так и для чего: Ну, let ... in ...
имеет широкий спектр приложений (большинство из которых не имеет ничего общего с монадами) и длительная история для загрузки. 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
. Если вы еще не знакомы с монадами (что-то вам нужно скоро изменить IMHO), проигнорируйте оператор >>=
и сосредоточьтесь на let
. Также обратите внимание, что отступы больше не имеют значения.
main = readFile "p8.log" >>= (\t ->
let digits = map digitToInt $ concat $ lines t
in print (problem_8 digits))
Ответ 4
Некоторые новички-новички о "следуют двум одинаковым".
Например, 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.
Btw, есть приятное объяснение со многими примерами о том, что на самом деле выполняют ключевые слова where
и in
.