Я решал довольно простую задачу: генерация всех убывающих последовательностей длиной L
, состоящих из натуральных чисел от 1
до M
в лексикографическом порядке. Тем не менее, я столкнулся с довольно странной проблемой. Посмотри:
c :: (Ord a, Num a, Enum a) => a -> a -> [[a]]
c m 1 = map return [1..m]
c m l = do
n <- [l..m]
res <- c (n - 1) (l - 1)
return $ n:res
c' :: (Ord a, Num a, Enum a) => a -> a -> [[a]]
c' m = helper 1 m where
helper :: (Ord a, Num a, Enum a) => a -> a -> a -> [[a]]
helper a b 1 = map return [a..b]
helper a b l = do
n <- [a..b]
True <- return $ (l - 1 <= n)
res <- helper a (n - 1) (l - 1)
return (n:res)
Итак, очевидно, что эти две функции делают абсолютно одно и то же (я проверял их на нескольких тестах, обе они дают правильные результаты на каждом), но если вы попытаетесь оценить c 100 98
и c' 100 98
в GHCi, вы увидите огромную разницу в время это занимает:
Как я уже говорил, результат тот же.
Итак, мне как-то неловко генерировать [a..b]
каждый раз, но я немного поспрашивал, и было предположение, что Haskell не выполняет сопоставление с образцом сразу, а задерживает его из-за ленивых вычислений, что вызывает огромное количество дополнительных вызовов c'
. Однако вторая теория не совсем подтвердилась: я установил точку останова в своем коде прямо из командной строки GHCi, чтобы отслеживать значение n
, что показало, что отложенное сопоставление с образцом не имеет места.
Может ли проблема быть на самом деле с функцией enumFromTo
или есть какая-то другая причина?
True <- return foo
вместоguard foo
? 2. Зачем передаватьa
, если оно никогда не меняется по сравнению с его первоначальным значением1
? 3. Почемуn <- [1..b]; guard (l-1 <= n)
вместоn <- [max 1 (l-1)..n]
или дажеn <- [l-1 .. n]
, если вы не боитесь таких рефакторингов? - person Daniel Wagner   schedule 04.03.2019