Haskell 是否有一个好的策略使一个功能成为生产功能?
编辑:我在这里所说的“懒惰”似乎不是“懒惰”的意思。我不确定正确的术语是什么。有些人说我正在寻找的术语是“生产性的”,但在这种情况下,我找不到该术语的任何定义。我想要的是一个可以处理无限列表的函数。我会用我对“懒惰”这个词的最好理解,把“懒惰”变成“富有成效” 功能Haskell 是否有一个好的策略使一个功能成为生产功能?,haskell,Haskell,编辑:我在这里所说的“懒惰”似乎不是“懒惰”的意思。我不确定正确的术语是什么。有些人说我正在寻找的术语是“生产性的”,但在这种情况下,我找不到该术语的任何定义。我想要的是一个可以处理无限列表的函数。我会用我对“懒惰”这个词的最好理解,把“懒惰”变成“富有成效” 功能 f a = a:(f (a-1)) h a = h (a ++ (map (-1) a)) 以高效的方式生成无限列表。因为a:在所有其他评估之前 这意味着你可以做取10(f0)就可以了 然而,功能 f a = a:(f (a-1
f a = a:(f (a-1))
h a = h (a ++ (map (-1) a))
以高效的方式生成无限列表。因为a:
在所有其他评估之前
这意味着你可以做取10(f0)
就可以了
然而,功能
f a = a:(f (a-1))
h a = h (a ++ (map (-1) a))
没有生产力,永远不会终止。因为a++
在另一个计算中。
因此,您无法执行头(h[0])
,即使它显然是0
是否有一个通用的策略可以将非生产性功能转变为生产性功能
具体来说,我试图解决的问题是,在懒散地使用其第二个参数的同时,使以下内容富有成效:
binarily a [] = a
binarily a (b:bs) = binarily (a ++ map (b+) a) bs
“一般策略”是定义函数,以便在递归之前计算结果值的一部分 在
f
中,最上面的表达式是(:)
函数的应用程序,它的第二个参数不严格。这意味着它甚至不需要计算f(a-1)
。如果您不需要列表的其余部分
在h
中,函数做的第一件事是递归-即它不会产生“部分结果”
您的二进制
函数实际上是“懒惰的”:它的第一个参数不严格,所以
take 10 $ binarily [1..] [1..5]
终止。
h
生成一个增长序列。在[0]
上,例如:
[0] ++
[0, -1] ++
[0, -1, -1, -2] ++
[0, -1, -1, -2, -1, -2, -2, -3] ++ ...
请注意,它显示了模式[x,fx,f(fx),…]
——在每一步中,您都要计算函数的一次迭代。这正是iterate::(a->a)->a->[a]
的目的,而++
s的折叠正是concat
:
h = concat . iterate go
where go x = x ++ map (subtract 1) x
下面是一个使用相同原理的二进制实现:
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
我们迭代函数并从流中提取元素,而
(snd
)不是。null
-如果它是无限的,那么它只需要整个流,然后我们concat
中间累加器(map fst
)
您会注意到,如果没有takeWhile
,您将得到一系列无限重复的元组,其中snd
是[]
。因此,我们所做的是流式处理,直到到达一个固定点,即将递归(fix
)转换为流式处理 你所寻找的恰当术语是高效,而不仅仅是“懒惰”
head(h[10])
根本没有定义。还原顺序为:h[10]=>h[10,9]=>h[10,9,9,8]=>h[10,9,9,8,7]=>…
。诚然,这个内部序列的头部总是相同的,10
,但缩减本身从未停止过。不,它与F10=>[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
生成,因为它们是由step函数返回的,该函数在列表:[x]
(您的(b:bs)
)中为每个x
调用。因此,只要我们忽略结果的fst
字段中的最终累积值,该函数也可以无限输入
显然,下一个结果的一部分出现在上一个结果中,可以立即返回:
binarily a bs = a ++ (concat . snd) (mapAccumL g a bs)
where
g a b = let -- for each b in bs:
res = map (b+) a -- this part of the result
anew = a ++ res -- next accumulator value
in (anew, res)
还有一种可能更“具体”的方法,将您的懒惰/高效的以二进制方式编写:
binarily a l = a ++ binRest a l
binRest a [] = []
binRest a (b:bs) = a' ++ binRest (a ++ a') bs
where
a' = map (b+) a
编辑:我被要求对我的思维过程进行一些解释。如果我们从二进制a(b1:b2:b3:…)开始,让我们先看看原始帖子中的二进制
在每一步中作为其第一个参数传递的内容:
很明显,我们可以立即生成a++
,然后在下一步map(b1+)
应用于此,因此@JonPurdy的答案中的直接concat$iterate…a
似乎应该会起作用。实际上,由于我们查看了bs
列表,因此scanl
函数比iterate
函数更匹配
但如果我们尝试这样做,我们会发现仍然存在不匹配:上面第三步中添加的部分不是第二步中添加到参数的部分的函数,而是第二步中添加到整个参数的函数。concat$scanl…
不太适合这一点
事实上,第一步生产的零件并不符合所有其他零件的规律
因此,我将其分为两个函数:首先,二进制地
,它处理第一步中要生成的内容,然后将所有其他步骤传递到binRest
其次,binRest
,它将迄今为止生成的所有内容作为第一个参数,并使用它来计算在这一步中生成的内容,然后递归。您的h
是惰性的,但它不会终止。没有条件告诉编译器何时停止应用h
。cf.是的,在第一个ar上它是不严格的gument,但不是第二个。我将编辑OP,使其更清晰。您的代码不会生成所需的输出,但我仍然不太确定您通过什么思维过程来获得此解决方案。scanl不适合,因为它只是累积;mapAccumL