强制严格评估 - 我做错了什么?

时间:2015-09-22 22:19:59

标签: haskell functional-programming seq

我希望在生成新结果之前计算中间结果以获得memoization的好处。

import qualified Data.Map.Strict as M
import Data.List
parts' m = newmap
    where
    n = M.size m + 1 
    lists =  nub $ map sort $ 
        [n] : (concat $ map (\i -> map (i:) (M.findWithDefault [] (n-i) m)) [1..n])
    newmap = seq lists (M.insert n lists m)

但是,如果我这样做

take 2000 (iterate parts' (M.fromList [(1,[[1]])]))

它仍然立即完成。

(可以使用数组代替地图帮助吗?)

3 个答案:

答案 0 :(得分:4)

简答:

如果您需要一次计算整个列表/数组/地图/...,您可以使用deepseq作为@JoshuaRahm建议,或使用($!!)运算符

以下答案如何强制执行严格性,但仅限于第1级(它会进行评估,直到达到可能包含(余数)表达式树的数据结构)。

此外,答案认为为什么懒惰和记忆不是(必然)彼此对立

更高级:

Haskell是一种懒惰的语言,它意味着只有计算的东西,如果绝对必要的话。表达式如下:

take 2000 (iterate parts' (M.fromList [(1,[[1]])]))

不会立即评估:Haskell只是存储这必须在以后计算。稍后,如果你真的需要第一个,第二个, i -th,或者列表的长度,它将评估它,甚至以一种懒惰的方式:如果你需要第一个元素,从它已经找到了计算该元素的方法,它将表示为:

element : take 1999 (<some-expression>)

然而,您可以强制Haskell使用exclamation mark (!)严格评估某些内容,这称为 strictness 。例如:

main = do
    return $! take 2000 (iterate parts' (M.fromList [(1,[[1]])]))

或者如果它是一个参数,你可以使用它:

f x !y !z = x+y+z

在此强制Haskell在&#34之前评估yz;增加表达式树&#34;为:

  

表达换X +表达式换-γ+表达式换-Z

编辑:如果您在let模式中使用它,可以use the bang as well

let !foo = take 2000 (iterate parts' (M.fromList [(1,[[1]])])) in ...

请注意,您只将结构折叠到第一级。因此,let !foo或多或少只会评估(_:_)

注意:请注意 memoization lazyness 彼此不是必需的。考虑清单:

numbers :: [Integer]
numbers = 0:[i+(sum (genericTake i numbers))|i<-[1..]]

如您所见,计算数字需要大量的计算工作量。数字表示为:

numbers ---> (0:[i+(sum (genericTake i numbers))|i<-[1..]])

但是,如果我评估numbers!!1,则必须计算第一个元素,它返回1;但也会评估numbers的内部结构。现在看起来像:

numbers (0:1:[i+(sum (genericTake i numbers))|i<-[2..]])

计算numbers!!1因此将&#34;帮助&#34;未来的计算,因为你永远不必重新计算列表中的第二个元素。

如果您计算numbers!!4000,则需要几秒钟。稍后如果你计算numbers!!4001,它几​​乎会立即计算出来。仅仅因为numbers!!4000已经完成的工作重用了

答案 1 :(得分:4)

数组可能会有所帮助,但您也可以尝试利用deepseq库。所以你可以写这样的代码:

let x = take 2000 (iterate parts' (M.fromList [(1,[[1]])])) in do
     x `deepseq` print (x !! 5)    -- takes a *really* long time
     print (x !! 1999)             -- finishes instantly

答案 2 :(得分:4)

您正在记住分区功能,但您的方法存在一些缺点:

  • 您只记得您必须事先指定的特定值
  • 您需要致电nubsort

以下是使用Data.Memocombinators的方法:

import Data.Memocombinators

parts = integral go
  where
    go k | k <= 0 = []  -- for safety
    go 1 = [[1]]
    go n = [[n]] ++ [ (a : p) | a <- [n-1,n-2..1], p <- parts (n-a), a >= head p ]

E.g:

ghci> parts 4
[[4],[3,1],[2,2],[2,1,1],[1,1,1,1]]

此备忘录是动态的,因此只会记住您实际访问的值。

请注意它是如何构建的 - parts = integral gogo使用parts进行任何递归调用。我们在这里使用integral组合子,因为parts是Int。

的函数