Почему этот оператор Haskell не выполняет ленивое вычисление?

У меня определена следующая функция:

ex 1 x = 1
ex 0 x = 0
ex b x = b ** x

Затем, когда я выполняю следующее:

1 `ex` (sum [1..])

он пытается вычислить сумму бесконечной последовательности, вместо того, чтобы лениться и возвращать 1. Почему?


РЕДАКТИРОВАТЬ: При дальнейшем исследовании я обнаружил, что лень возникает, если я определяю функцию ex в файле, но не, если я определяю ее в GHCI:

$ ghci
GHCi, version 6.8.2: http://www.haskell.org/ghc/  :? for help
Loading package base ... linking ... done.
Prelude> let ex 1 x = 1
Prelude> let ex b x = b ** x
Prelude> ex 1 (sum [1..])
<interactive>: out of memory (requested 1048576 bytes)

Если я перенесу определение ex в файл (в данном случае test.hs):

$ ghci
GHCi, version 6.8.2: http://www.haskell.org/ghc/  :? for help
Loading package base ... linking ... done.
Prelude> :load test.hs 
[1 of 1] Compiling Main             ( test.hs, interpreted )
Ok, modules loaded: Main.
*Main> ex 1 (sum [1..])
1.0

Возникает новый вопрос: почему?


person perimosocordiae    schedule 08.07.2010    source источник
comment
Нет, 1 ex (сумма [1 ..]) действительно возвращает 1.0 на моем компьютере. Какая версия ghc и т. Д.? И получите ли вы тот же результат с ex 1 (sum [1 ..])?   -  person ShreevatsaR    schedule 08.07.2010
comment
@ShreevatsaR: Хорошие моменты, см. Мое обновление выше.   -  person perimosocordiae    schedule 08.07.2010


Ответы (3)


В GHCi каждый оператор let вводит новое определение ex вместо нескольких вариантов шаблона, как вы ожидали. Таким образом, он зависает, потому что, когда вы вводите ex 1 (sum [1..]) впоследствии, существует только окончательная ex b x = b ** x версия.

Если вы хотите определить функцию с несколькими шаблонами в GHCi, вам нужно поместить ее в один оператор let, например:

let ex 1 x = 1; ex 0 x = 0; ex b x = b ** x

То же самое относится и ко всему остальному, что обычно записывается в несколько строк, например к обозначению do. Например, такая функция:

f x = do
    y <- get
    put (x + y)
    return y

В GHCi нужно было бы написать так:

let f x = do { y <- get; put (x + y); return y }
person C. A. McCann    schedule 08.07.2010
comment
Ага, это действительно неудобно. Тем не менее, это полезно знать, спасибо! - person perimosocordiae; 08.07.2010
comment
@perimosocordiae: Как довольно активный пользователь GHCi, я обычно привык писать большую часть или все свои определения во внешнем файле, загружать файл с помощью GHCi и использовать REPL в основном для оценки простых выражений. - person C. A. McCann; 08.07.2010
comment
Вы также можете использовать :set +m для записи определений в несколько строк. - person aleator; 29.04.2013
comment
@aleator: Я считаю, что функция новее, чем этот пост. Я содрогаюсь при мысли, сколько из моих ответов теперь глупо в свете новых функций и библиотек GHC. - person C. A. McCann; 29.04.2013

Prelude> let ex 1 x = 1
Prelude> let ex b x = b ** x

Здесь вы не определяете функцию с двумя случаями. Вы определяете функцию с одним регистром, а затем определяете ее снова, отменяя предыдущее определение.

Чтобы определить одну функцию с двумя шаблонами, используйте let ex 1 x = 1; ex b x = b ** x, т.е. разделите регистры точкой с запятой.

person sepp2k    schedule 08.07.2010

Я упустил одну лень, из-за которой приведенный ниже ответ неверен.


Потому что sum вычисляет общую сумму всех элементов в последовательности. Что в вашем случае бесконечно.

Ты наверное хочешь

map ((curry ex) 1) [1..]

То есть

map -- map each item x to y
    (
        (
            curry ex -- curry ex, to transform (x, y) -> z into x -> y -> z
        )
        1 -- then invoke it with 1, which results in y -> z, x being 1
    )
    [1..] -- the infinite sequence to be mapped.
person Dykam    schedule 08.07.2010
comment
-1: он будет оценивать сумму только в том случае, если она действительно нужна, а в данном случае это не так. - person Zifre; 08.07.2010