Как Haskell имеет позднюю оценку? - PullRequest
3 голосов
/ 02 декабря 2019

Я слышал, что у Хаскелла поздняя оценка. Однако все, что я пытался сделать, казалось, оценило это так же, как любой другой язык программирования;

рассмотрим следующий код:

test :: Bool -> IO()
test n = do
  let y = 5
  print n

main = do
  let y = 8
  test (y == 8)

Этот код выводит:

True
  • Почему этот код оценивает "y" раньше?
  • Могу ли я привести пример поздней оценки в Haskell?

Ответы [ 2 ]

9 голосов
/ 02 декабря 2019

Прежде всего, следует иметь в виду некоторые важные общие сведения:

Переменные Haskell неизменны .
Всегда, без исключения, и вы ничего не можетеделать с этим.

Поэтому, когда вы пишете что-то вроде let y = 5, никогда не изменяет значение какой-либо уже существующей переменной. Скорее, он вводит новую переменную с именем y и дает ей желаемое значение. Где-нибудь еще в вашей программе есть переменная, которую вы назвали y, не имеет значения, это вообще другая переменная. Фактически, учтите следующее:

main :: IO ()
main = do
   let y = 1
   do let y = 2
      print y
   print y

Вывод:

2
1

В вашем примере оператор let y = 5 вообще не имеет никакого эффекта и, скорее всего, будет просто отброшенкомпилятор вообще. Фактически, если вы скомпилируете с -Wall (как следует), GHC сообщит вам, что:

/tmp/wtmpf-file30239.hs:4:7: warning: [-Wunused-local-binds]
    Defined but not used: ‘y’
  |
4 |   let y = 5
  |       ^

Так, в частности, проверка y == 8 не может быть затронута каким-либо оператором let y =Вы могли бы использовать.

На самом деле, в общем, ленивая оценка не влияет на значения. Это одна из замечательных особенностей чисто функционального языка: поскольку все постоянно, ленивая оценка, как правило, не влияет на семантику значений, она влияет только на порядок, в котором выполняется та же самая работа - что может влияет на то, как быстро что-то завершается (или заканчивается ли оно вообще), но не на то, какое значение оно производит, когда оно сделано. Не имеет значения, оценивается ли y == 8 до или после того, как среда выполнения входит в функцию test, и на самом деле стандарт Haskell ничего об этом не говорит - все, что он говорит, это то, что если test заканчивается даже если не использовать значение аргумента, то не определяющий аргумент не должен препятствовать завершению test ( не строгая семантика ). Итак, следующее демонстрирует ленивую оценку в действии:

unobtanium :: Bool
unobtanium = unobtanium -- infinite loop

don'tTest :: Bool -> IO ()
don'tTest a = do
   putStrLn "Meh, I'm too lazy to do it."

main :: IO ()
main = do
   let y = unobtanium
   don'tTest y

... даже если y невозможно оценить.

3 голосов
/ 02 декабря 2019

Почему этот код выполняет раннюю оценку?

Это не так, как обсуждал @leftaroundabout. Недоразумение здесь касается переменной области видимости, а не оценки.

могу ли я привести пример поздней оценки в Haskell?

Бесконечные списки

Я обнаружил, что самыми ранними примерами ленивых вычислений были бесконечные списки. Рассмотрим типичную последовательность Фибоначчи. Подробная версия имени сакэ:

fibs :: [Integer]
fibs = 0 -- First element
     : 1 -- Cons with the second element
     : zipWith (\first second -> first + second) fibs (drop 1 fibs)
         -- Cons with all other elements, dynamically computed.

Некоторым людям не нравятся вычисления. Примером для этих людей является повторяющийся список. Рассмотрим бесконечный связанный список из 42 ... это может быть трудно сделать полезным способом в C.

infiniteFortyTwos = 42 :infiniteFortyTwos

Ошибки

Возможно, ошибкинаиболее распространенная ленивая оценка, но большинство людей на самом деле не осознают, что ленивая оценка имеет место. Часто называемый коротким замыканием при использовании с логической операцией, рассмотрим сценарий оболочки:

var=$(ls /dir/that/does/not/exist 2>/dev/null || echo DNE)

Эта идиома встречается повсеместно в программировании и разработке. Perl часто использовал || die "error" в первые годы. В Haskell типы препятствуют смешиванию оценки и неудачи таким специфическим образом, но ленивая функциональность одинакова.

Рассмотрим эту плохо продуманную подпрограмму:

headEquals :: Eq a => a -> [a] -> Bool
headEquals v xs = not (null xs) && v == head xs

Или обычные java и Cузоры:

if(ptr != NULL && *ptr == value) {
   ...
}

Галстук Узел

Гораздо более продвинутая ленивая методика оценки - завязывание узла . Например, представьте, что мы хотим заменить все элементы в списке на самый маленький элемент в списке. Давайте представим, что у нас есть ответ при вычислении ответа, а затем используем возвращенное значение в качестве указанного ответа:

minList :: [Int] -> [Int]
minList [] = []
minList (m:xs) =
    let (theMin, newList) = go theMin m xs
    in theMin : newList
  where
   go realAnswer partialAnswer []     = (partialAnswer, [])
   go realAnswer partialAnswer (y:ys) =
        let newPartialAnswer = min y partialAnswer
        in (realAnswer :) <$> go realAnswer newPartialAnswer ys
...