Дано:
λ: let f = putStrLn "foo" in 42
42
Что такое тип f
? Почему "foo"
не распечатывается перед показом результата 42
?
Наконец, почему не работает следующее?
λ: :t f
<interactive>:1:1: Not in scope: ‘f’
Дано:
λ: let f = putStrLn "foo" in 42
42
Что такое тип f
? Почему "foo"
не распечатывается перед показом результата 42
?
Наконец, почему не работает следующее?
λ: :t f
<interactive>:1:1: Not in scope: ‘f’
Что такое
f
type?
Как вы правильно определили, это IO ()
, которое можно рассматривать как действие IO, которое ничего не возвращает ничего полезного (()
)
Почему "foo" не печатается перед показом результата
42
?
Haskell лениво оценивается, но даже seq
в этом случае недостаточно. Действие IO будет выполняться только в REPL, если выражение возвращает действие IO. Действие IO будет выполняться только в программе, если оно возвращается main
. Однако есть способы обойти это ограничение.
Наконец, почему не работает следующее?
Haskell let
называет значение в пределах области выражения, поэтому после оценки выражения f
выходит за рамки.
let f = ...
просто определяет f
и ничего не запускает. Это смутно похоже на определение новой функции в императивном программировании.
Ваш полный код let f = putStrLn "foo" in 42
можно свободно перевести на
{
function f() {
print("foo");
}
return 42;
}
Вы не ожидали, что вышеприведенный текст ничего не напечатает, верно?
Для сравнения, let f = putStrLn "foo" in do f; f; return 42
похож на
{
function f() {
print("foo");
}
f();
f();
return 42;
}
Соответствие не совершенное, но, надеюсь, вы получите эту идею.
f
будет иметь тип IO ()
.
"foo" не печатается, потому что f
не привязан к реальному миру. (Я не могу сказать, что это дружеское объяснение. Если это звучит глупо, вам может понадобиться отнестись к некоторому учебнику, чтобы поймать идею Монады и ленивую оценку).
let name = value in (scope)
делает значение доступным, но не вне области видимости, поэтому :t
не найдет его в области верхнего уровня ghci.
let
без in
делает его доступным для :t
(этот код действителен только в ghci):
> let f = putStrLn "foo"
> :t f
f :: IO ()
Здесь есть две вещи.
Сначала рассмотрим
let x = sum [1..1000000] in 42
Хаскелл ленив. Поскольку мы ничего не делаем с помощью x
, он никогда не вычисляется. (Это так же хорошо, потому что это будет мягко медленно.) Действительно, если вы скомпилируете это, компилятор увидит, что x
никогда не используется и не удаляет его (т.е. Не сгенерировать скомпилированный код для него).
Во-вторых, вызов putStrLn
фактически ничего не печатает. Скорее, он возвращает IO ()
, который вы можете рассматривать как своего рода "объект команды ввода-вывода". Просто наличие объекта команды отличается от его выполнения. По дизайну единственный способ "выполнить" объект команды ввода-вывода - вернуть его из main
. По крайней мере, это полная программа; GHCi имеет полезную функцию: если вы введете выражение, которое возвращает объект команды ввода-вывода, GHCi выполнит его для вас.
Ваше выражение возвращает 42; снова, f
не используется, поэтому ничего не делает.
Как chi
справедливо указывает, он немного напоминает объявление локальной (нулевой аргумент) функции, но никогда не вызывает ее. Вы не ожидаете увидеть какой-либо вывод.
Вы также можете сделать что-то вроде
actions = [print 5, print 6, print 7, print 8]
Создает список объектов команды ввода-вывода. Но, опять же, он не выполняет ни одного из них.
Обычно, когда вы пишете функцию, которая делает I/O, это блок блокировки, который объединяет все в один гигантский объект команды ввода-вывода и возвращает его вызывающему. В этом случае вам не нужно разбираться в этом различии между определением объекта команды и ее выполнением. Но различие все еще существует.
Возможно, это проще увидеть с помощью монады с явной функцией запуска. Например, runST
принимает объект команды ST, запускает его и возвращает ответ. Но (скажем) newSTVar
сам по себе ничего не делает, кроме как построить команду ST; вы должны runST
, прежде чем что-либо на самом деле "произойдет".