是否有一个很好的策略来使一个功能成为一个有效的功能?

时间:2014-10-29 13:47:41

标签: haskell

编辑:看来我在这里称之为“懒惰”,不是“懒惰”的意思。我不确定适当的术语是什么。有些人说我正在寻找的术语是“富有成效的”,但在这种情况下我找不到该术语的任何定义。我想要的是一个可以使用无限列表的函数。我将使用我对该术语的最佳理解,将任何“懒惰”变为“富有成效”。

功能

f a = a:(f (a-1))

以高效的方式生成无限列表。因为a:在所有其他评估面前。

这意味着您可以执行take 10 (f 0)并且没问题。

然而,功能

h a = h (a ++ (map (-1) a))

没有效率,永远不会终止。由于a ++在另一个评估范围内。 因此,即使很明显它是0,也不能head (h [0])

我是否可以采用一般策略将非生产性功能转变为生产性功能?

具体来说,我试图解决的问题是在懒洋洋地消耗第二个参数的同时使下面的内容富有成效:

binarily a [] = a
binarily a (b:bs) = binarily (a ++ map (b+) a) bs

4 个答案:

答案 0 :(得分:6)

h生成一个不断增长的序列。以[0]为例:

[0] ++
[0, -1] ++
[0, -1, -1, -2] ++
[0, -1, -1, -2, -1, -2, -2, -3] ++ ...

请注意,它显示了模式[x, f x, f (f x), …] - 在每一步中,您都在计算该函数的一次迭代。这正是iterate :: (a -> a) -> a -> [a]的用途,++ s的折叠恰好是concat

h = concat . iterate go
  where go x = x ++ map (subtract 1) x

以下是使用相同原则的binarily的一个实现:

binarily a bs
  = concatMap fst
  . takeWhile (not . null . snd)
  $ iterate go (a, bs)
  where
  go (acc, b : bs) = (acc ++ map (b +) acc, bs)
  go x = x

我们iterate来自流take Whilebs)的函数和snd元素是not . null - 如果它是无限的,那么这只需要整个流 - 然后我们concat中间累加器(map fst)。

你会注意到,如果那里没有takeWhile,你最终会得到一系列无限重复的元组,其中snd[]。所以我们正在做的是流式传输,直到我们达到固定点,即将递归(fix)转换为流式传输。 :)

答案 1 :(得分:2)

'一般策略'是定义你的函数,以便它可以在递归之前计算结果值的一部分。

f中,最顶层的表达式是(:)函数的应用程序,它在第二个参数中是非严格的。这意味着它甚至不需要评估f (a-1)。如果你不需要列表的其余部分。

h中,函数的第一件事是递归 - 即它不会产生“部分结果”。

你的binarily函数实际上是“懒惰的”:它的第一个参数是非严格的,所以

take 10 $ binarily [1..] [1..5]

终止。

答案 2 :(得分:2)

您正在寻找的正确术语高效,而不仅仅是“懒惰”......

根本没有定义

head (h [10])。缩减序列为:h [10] => h [10,9] => h [10,9,9,8] => h [10,9,9,8,9,8,8,7] => ...。确实,这个内部序列的头部始终是相同的,10,但减少本身永远不会停止。不,它与f 10 => [10,9,8,7,...不一样。

你的功能,

binarily a [] = a
binarily a (b:bs) = binarily (a ++ map (b+) a) bs
{-  try it out with [b1,b2,b3,b4] :
a                          b1     the arguments received;
a1@(a  ++ (map (b1+) a))   b2     if we've already produced a, we just need 
                                       to produce  (map (b1+) a)  next
a2@(a1 ++ (map (b2+) a1))  b3     if we've already produced a1, we just need 
                                       to produce  (map (b2+) a1)  next
a3@(a2 ++ (map (b3+) a2))  b4     ai@... name the interim values
a4@(a3 ++ (map (b4+) a3))  []     a4 is returned  -}

相当于

import Data.List (mapAccumL)

-- mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])

binarily a bs = (last . snd) (mapAccumL g a bs)
 where
   g a b = let anew = a ++ map (b+) a
           in (anew, anew)             -- (next_accum, part_result)

mapAccumL捕获了同时累积和生成完整结果部分的模式。列表:: [y](其返回值的snd字段)是懒惰生成的,由所有y构建,因为它们由为每个{{1}调用的步进函数返回列表x(您的:: [x])。因此,只要我们忽略结果的(b:bs)字段中的最终累积值,该函数也可以使用无限输入。

显然,下一个结果的一部分出现在上一个结果中,可以立即返回:

fst

答案 3 :(得分:2)

又一个,也许更多"具体"写作懒惰/富有成效的方式binarily

binarily a l = a ++ binRest a l

binRest a [] = []
binRest a (b:bs) = a' ++ binRest (a ++ a') bs
  where
    a' = map (b+) a
编辑:有人要求我解释我的思考过程。如果我们从binarily开始,让我们首先看一下原始帖子中的binarily a (b1:b2:b3:...)作为第一个参数的开始,如果我们从a a ++ map (b1+) a a ++ map (b1+) a ++ map (b2+) (a ++ map (b1+) a) a ++ map (b1+) a ++ map (b2+) (a ++ map (b1+) a) ++ map (b3+) (a ++ map (b1+) a ++ map (b2+) (a ++ map (b1+) a)) 开始:

a ++

很明显,我们可以立即生成map (b1+),然后在下一步concat $ iterate ... a应用于此,因此@ {JonPurdy'首先回答它似乎应该有效。实际上,由于我们通过bs列表,scanl函数比iterate更匹配。

但是如果我们尝试一下,我们会发现仍然存在不匹配:上面第三步中添加的部分不是第二步中添加部分的函数,而是第二步中的整个参数。 concat $ scanl ...并不适合这种情况。

事实证明,第一个步骤中生成的部分并不适合所有其他部分的常规模式。

因此,我分为两个函数:第一个是binarily,它处理第一步中要生成的内容,然后传递给binRest以执行所有其他步骤。

其次,binRest,它将迄今为止生成的所有内容作为其第一个参数,并使用它来计算在此步骤中生成的内容,然后进行递归。