Насколько Хаскелл ленив?
Мне нужно разъяснение лени с Haskell.
Если у меня есть эта функция:
myFunction arg
| arg == 1 = a
| arg == 2 = a*b
| arg == 3 = b+c
| otherwise = (a+b)*c
where
a = ...
b = ...
c = ...
d = ...
Когда я вызываю myFunction 1
, Haskell будет оценивать только a = ...
, ни b, ни c, ни д.
Но если я пишу
myFunction arg
| arg == 1 = a
| arg == 2 = a*b
| arg == 3 = b+c
| otherwise = (a+b)*c
where
(a,b,c,d) = anotherFunction arg
Каким будет поведение Haskell?
- Будет ли он оценивать только a и "распространять" ленивость на
anotherFunction
?
- Или он будет оценивать весь набор (a, b, c, d) как результат
anotherFunction
?
Ответы
Ответ 1
В обоих случаях он ничего не будет оценивать, если это не требуется. Один из способов потребовать значение - это вызов функции в ghci (который печатает значение в ghci
и, следовательно, требует его). Предполагая, что вы выполняете функцию, тогда в вашем втором случае она будет оценивать кортеж слабой головой нормальной формы (WHNF), а затем оценивать первый элемент в (a,b,c,d)
потому что требуется только это значение. Остальные элементы b
, c
и d
будут находиться в форме thunk. Фактически вы можете убедиться в этом сами:
myFunction arg
| arg == 1 = a
| arg == 2 = a*b
| arg == 3 = b+c
| otherwise = (a+b)*c
where
(a,b,c,d) = anotherFunction arg
anotherFunction x = (x, undefined, undefined, undefined)
Демо в ghci:
λ> myFunction 1
1
Ответ 2
Ну, его интересует только a
, поэтому это означает, что существует неявная функция:
thea :: (a,b,c,d) -> a
thea (a,_,_,_) = a
Другими словами, Haskell не интересуется другими элементами кортежа. Иногда, однако, элементы кортежа имеют некоторую структуру. Скажем, другая функция определяется как:
anotherFunction :: Int -> (Int,Int,Int,Int)
anotherFunction x = (z,t,f,g)
where f = x*x
g = f+x
z = g-2
t = 3
В этом случае - для оценки первого элемента - также будет оценен третий и четвертый элементы. Но поскольку вы ничего не делаете с ними, Haskell не будет интересоваться их результатом.
Ответ 3
Как уже отмечалось, оценивается только a
.
Помните, что для использования лени важно, чтобы anotherFunction
возвращал кортеж перед оценкой его компонентов. Например, рассмотрим
anotherFunction n = if p > 1000 then (n, p) else (n, 0)
where p = product [1..n]
Вышеуказанное всегда будет оценивать product [1..n]
, даже если вызывающему абоненту требуется только первый компонент пары (который равен n
). Это связано с тем, что if
необходимо оценить до того, как пара может быть возвращена, и это заставляет p
. Напротив,
anotherFunction n = (n, if p > 1000 then p else 0)
where p = product [1..n]
немедленно вернет пару. Если оценивается только его первый компонент, то p
не будет вычислен вообще.
Ответ 4
Если нет необходимости получать значение этой переменной, оно не будет оцениваться. В принципе, Haskell настолько ленив, если не сказано, что это не так.
Вы можете подтвердить это, как это
Prelude> :set +m
Prelude> let anotherFunction = (100, 1 `div` 0)
Prelude|
Prelude> let myFunction arg
Prelude| | arg == 1 = a
Prelude| | otherwise = b
Prelude| where
Prelude| (a, b) = anotherFunction
Prelude|
Здесь 1 `div` 0
поднимет ошибку divide by zero
. Если он оценивает все элементы, то даже когда вы вызываете myFunction
с 1
, вы получили бы эту ошибку, но
Prelude> myFunction 1
100
только когда вы вызываете его с любым другим значением, необходимо оценить второе значение кортежа, и он потерпит ошибку с ошибкой divide by zero
.
Prelude> myFunction 2
*** Exception: divide by zero