2009-05-07 4 views
31

У меня есть код Haskell, который делает работает правильно в бесконечном списке, но я не понимаю , почему он может сделать это успешно. (Я изменил свой исходный код, который не обрабатывал бесконечные списки, - чтобы включить что-то из какого-либо другого кода в Интернете, и вдруг я вижу, что он работает, но не знает почему).Почему этот код Haskell успешно работает с бесконечными списками?

myAny :: (a -> Bool) -> [a] -> Bool 
myAny p list = foldr step False list 
    where 
     step item acc = p item || acc 

Мое понимание foldr является то, что он будет цикл через каждый элемент списка (и, возможно, что понимание является неполным). Если это так, не имеет значения, как выражается функция «шаг» ... код не должен обрабатывать бесконечные циклы.

Однако следующие работы:

*Main Data.List> myAny even [1..] 
True 

Пожалуйста, помогите мне понять: почему ??

ответ

46

Давайте сделаем небольшой след в наших головах того, как Haskell будет оценивать ваше выражение. Подставив равно для равных по каждой строке, выражение довольно быстро вычисляется в True:

myAny even [1..] 
foldr step False [1..] 
step 1 (foldr step False [2..]) 
even 1 || (foldr step False [2..]) 
False || (foldr step False [2..]) 
foldr step False [2..] 
step 2 (foldr step False [3..]) 
even 2 || (foldr step False [3..]) 
True || (foldr step false [3..]) 
True 

Это работает, потому что acc передается как невычисленный стук (ленивая оценка), но и потому, что функция || строго в его первого аргумент.

Так это заканчивается:

True || and (repeat True) 

Но это вовсе не так:

and (repeat True) || True 

Посмотрите на определение || чтобы понять, почему это так:

True || _ = True 
False || x = x 
+4

Кроме того, вы можете убедиться, что код не вычисляет более двух элементов с помощью: 'myAny p list = foldr (\ ia -> trace (show i) (pi || a))' - который будет показывать только '1 2 True' – viraptor

+0

Вау, это был ОЧЕНЬ хороший ответ на открытие глаза. Прежде всего, я не начинал с определения foldr передо мной. Я предположил, что его код будет использовать расширенные функции, которые я пока не знаю, поэтому я рассматривал его только как последнее средство. Ваш ответ заставил меня взглянуть, и это многое проясняет. foldr сам использует «обычную старую» структурную рекурсию. Мне нравится, как ты сломал это. Благодарю. –

+0

BTW, является || полностью строгий в своем первом аргументе, или он просто «отдаёт предпочтение» своему первому аргументу? Например, что, если arg 2 уже был оценен, но arg 1 все еще был просто тонким? И скажем, arg 2 был ложным. Будет ли Haskell короткое замыкание в противоположном направлении? Благодарю. –

1

Ключевым моментом здесь является то, что Haskell является нестрогим языком. «Нестрогий» означает, что он позволяет выполнять нестрогие функции, что, в свою очередь, означает, что параметры функции могут не полностью оцениваться до их использования. Это, очевидно, допускает ленивую оценку, которая является «методом задержки вычисления, пока результат не потребуется».

Начинает this Wiki article

+0

ОК, я знал это о ленивой оценке. Но мне нужна помощь в подключении точек от этого, чтобы он работал над этим кодом. Между тем, я проверю статью wiki. Благодарю. –

1

Я не знаю Haskell, но я подозреваю, что в вашем случае, это работает из-за ленивую оценку. Поскольку он позволяет работать со списком бесконечно долго, когда вы обращаетесь к нему, он будет вычислять результат по мере необходимости.

См http://en.wikipedia.org/wiki/Lazy_evaluation

+0

Это хорошая статья, спасибо за ссылку. –

18

Мое понимание foldr является то, что она будет цикл по каждому элементу в списке (и, возможно, что понимание является неполным).

foldr (в отличие от foldl) не Переберите каждый элемент списка. Поучительно посмотреть, как определяется foldr.

foldr f z []  = z 
foldr f z (x:xs) = f x (foldr f z xs) 

Когда вызов foldr оценивается, она заставляет оценку вызова функции f. Но обратите внимание, как рекурсивный вызов foldr встроен в аргумент функции f. Этот рекурсивный вызов не оценивается, если f не оценивает свой второй аргумент.

+0

Хорошая точка. Я изначально не знал, что определение скрепок будет понятно на моем уровне знаний Хаскелла. Я замечаю, как вы указываете, что рекурсивный вызов был намеренно встроен в аргумент функции, чтобы сделать его тонким. Это что-то, что Haskell dev's делает для себя? Существуют ли случаи, когда вам не нужна функция, но вы создаете ее просто для того, чтобы вы могли передать аргумент и знать, что это будет битком? –

+2

Charlie, так как это Haskell, который мы используем, * ничего * оценивается, если только это не заставляет его. –