Haskell Lazy Оценка и повторное использование - PullRequest
11 голосов
/ 26 декабря 2010

Я знаю, что если бы я вычислил список квадратов в Хаскеле, я мог бы сделать это:

squares = [ x ** 2 | x <- [1 ..] ]

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

print $ take 4 squares

Иэто распечатало бы [1.0, 4.0, 9.0, 16.0].Это оценивается как [1 ** 2, 2 ** 2, 3 ** 2, 4 ** 2].Теперь, поскольку Haskell функционален и результат будет одинаковым каждый раз, если бы мне пришлось снова вызывать квадраты где-нибудь еще, пересмотрел бы ли он уже вычисленные ответы?Если бы я использовал повторно квадраты после того, как уже вызвал предыдущую строку, будет ли он пересчитывать первые 4 значения?

print $ take 5 squares

Будет ли он оценивать [1.0, 4.0, 9.0, 16.0, 5 ** 2]?

Ответы [ 4 ]

19 голосов
/ 26 декабря 2010

В этом случае он не будет пересчитан, поскольку список фактически создается, а список квадратов продолжает существовать после вызова. Однако функции Haskell в целом не запоминаются. Это работает только в таком случае, когда вы явно не вызываете функцию, а просто исследуете (в) конечный список.

13 голосов
/ 26 декабря 2010

Это значение squares потенциально полиморфно:

Prelude> :t [ x ** 2 | x <- [1 ..] ]
[ x ** 2 | x <- [1 ..] ] :: (Floating t, Enum t) => [t]

AFAIK, будет ли он пересчитан (в GHC) или нет, зависит от того, имеет ли значение верхнего уровня squares полиморфный тип. Я считаю, что GHC не запоминает полиморфные значения, включающие классы типов (функции от типов к значениям), так же, как он не запоминает обычные функции (функции от значений к значениям).

Это означает, что если вы определите squares как

squares :: [Double]
squares = [ x ** 2 | x <- [1 ..] ]

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

squares :: (Floating t, Enum t) => [t]
squares = [ x ** 2 | x <- [1 ..] ]

тогда он, вероятно, будет вычисляться при каждом использовании, даже если он используется несколько раз в одном и том же типе. (Я не проверял это, хотя, и возможно, что GHC, если он видит несколько вариантов использования squares :: [Double], может специализировать значение squares для этого типа и разделять полученное значение.) Конечно, если используется squares для нескольких различных типов, таких как squares :: [Double] и squares :: [Float], он будет пересчитан.

Если вы не дадите никакой сигнатуры типа для squares, то к ней будет применено ограничение мономорфизма , если оно не отключено. В результате squares будет присвоен мономорфный тип, выведенный из остальной части вашей программы (или в соответствии с правилами по умолчанию). Цель ограничения мономорфизма состоит в том, чтобы гарантировать, что значения, которые выглядят так, как будто они будут оцениваться только один раз, например, squares, действительно будут оцениваться только один раз.

2 голосов
/ 01 апреля 2015

Почему бы не использовать ghci для проверки (если ghc ваш компилятор):

Prelude> let squares = [ x ** 2 | x <- [1 ..] ] :: [Float]
Prelude> :print squares
squares = (_t6::[Float])

Так что все ghci сейчас знает, что у вас есть список.

Prelude> print $ take 4 squares
[1.0,4.0,9.0,16.0]
Prelude> :print squares
squares = 1.0 : 4.0 : 9.0 : 16.0 : (_t7::[Float])

Знай, знает, что твой список начинается с материала, потому что ему нужно было его распечатать.

Prelude> let z = take 5 squares
Prelude> :print z
z = (_t8::[Float])

взять 5 само по себе ничего не оценивает

Prelude> length z --Evaluate the skeleton
5
Prelude> :print z
z = [1.0,4.0,9.0,16.0,(_t9::Float)]

Из-за длины take начинает двигаться, и мы видим, что вы были правы. Вы также можете проверить, что происходит, когда оно полиморфно, просто опуская определение типа на квадратах. Еще один хороший трюк, если вы не хотите использовать ghci, это использовать в своем коде undefined (ваша программа вылетает именно тогда, когда она пытается оценить _|_, типом которого является undefined)

2 голосов
/ 26 декабря 2010

Чтобы уточнить ответ, который уже дан, это функция Haskell:

thisManySquares n = map (^2) [1..n]

Итак, если вы заменили вызовы thisManySquares 4 на take 4 squares, тогда да, функция будет вызываться повторно.

...