Haskell:方程扩展器 1+(1+(1+(1+(…))))=∞
Haskell 是否存在方程扩展器?
类似 foldr.com 的内容:1+(1+(1+(1+(…))) )=∞
我是 Haskell 的新手,我无法理解为什么某些方程比其他方程更可取。我认为如果我能看到方程的扩展将会有所帮助。
例如,我发现 foldr
与 foldl
一开始很难理解,直到我看到它们展开。
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr k z xs = go xs
where
go [] = z
go (y:ys) = y `k` go ys
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f z0 xs0 = lgo z0 xs0
where
lgo z [] = z
lgo z (x:xs) = lgo (f z x) xs
从定义中我可以看到 foldr
像这样扩展:
foldr (+) 0 [1..1000000] -->
1 + (foldr (+) 0 [2..1000000]) -->
1 + (2 + (foldr (+) 0 [3..1000000])) -->
1 + (2 + (3 + (foldr (+) 0 [4..1000000]))) -->
1 + (2 + (3 + (4 + (foldr (+) 0 [5..1000000])))) -->
并且 foldl
像这样扩展:
foldl (+) 0 [1..1000000] -->
foldl (+) (foldl (+) 0 [1]) [2..1000000]) -->
foldl (+) (foldl (+) (foldl (+) 0 [1])) [3..1000000]) -->
或者来自 Haskell Wiki on Foldr Fold Foldl':
let z1 = 0 + 1
in foldl (+) z1 [2..1000000] -->
let z1 = 0 + 1
z2 = z1 + 2
in foldl (+) z2 [3..1000000] -->
let z1 = 0 + 1
z2 = z1 + 2
z3 = z2 + 3
in foldl (+) z3 [4..1000000] -->
let z1 = 0 + 1
z2 = z1 + 2
z3 = z2 + 3
z4 = z3 + 4
in foldl (+) z4 [5..1000000] -->
但是,我在更大的方程上遇到了困难,无法理解为什么事情会像 Haskell 中那样工作。例如,第一个筛函数使用 1000 个过滤器,而第二个筛函数只需要 24 个过滤器即可找到 1001 个素数。
primes = sieve [2..]
where
sieve (p:xs) = p : sieve [x | x <- xs, rem x p /= 0]
primes = 2: 3: sieve (tail primes) [5,7..]
where
sieve (p:ps) xs = h ++ sieve ps [x | x <- t, rem x p /= 0]
-- or: filter ((/=0).(`rem`p)) t
where (h,~(_:t)) = span (< p*p) xs
我花了很长时间手动进行锻炼和扩展。我开始明白它是如何运作的。然而,扩展某些表达式的自动化工具将极大地提高我对 Haskell 的理解。
此外,我认为它还可以帮助解决寻求优化 Haskell 代码的问题:
- 优化 Haskell 代码
- < a href="https://stackoverflow.com/questions/4378194/help-optimize-my-haskell-code-calculate-the-sum-of-all-the-primes-below-two-mil">帮助优化我的haskell代码-计算200万以下所有素数的和
有扩展Haskell表达式的工具吗?
Does there exist a equation expander for Haskell?
Something like foldr.com: 1+(1+(1+(1+(…))))=∞
I am new to Haskell I am having trouble understanding why certain equations are more preferable than others. I think it would help if I could see the equations expanded.
For example I found foldr
vs foldl
difficult to understand at first until I saw them expanded.
foldr :: (a -> b -> b) -> b -> [a] -> b
foldr k z xs = go xs
where
go [] = z
go (y:ys) = y `k` go ys
foldl :: (a -> b -> a) -> a -> [b] -> a
foldl f z0 xs0 = lgo z0 xs0
where
lgo z [] = z
lgo z (x:xs) = lgo (f z x) xs
From the definitions I can see that foldr
expands like this:
foldr (+) 0 [1..1000000] -->
1 + (foldr (+) 0 [2..1000000]) -->
1 + (2 + (foldr (+) 0 [3..1000000])) -->
1 + (2 + (3 + (foldr (+) 0 [4..1000000]))) -->
1 + (2 + (3 + (4 + (foldr (+) 0 [5..1000000])))) -->
and foldl
expands like this:
foldl (+) 0 [1..1000000] -->
foldl (+) (foldl (+) 0 [1]) [2..1000000]) -->
foldl (+) (foldl (+) (foldl (+) 0 [1])) [3..1000000]) -->
or from Haskell Wiki on foldr fold foldl':
let z1 = 0 + 1
in foldl (+) z1 [2..1000000] -->
let z1 = 0 + 1
z2 = z1 + 2
in foldl (+) z2 [3..1000000] -->
let z1 = 0 + 1
z2 = z1 + 2
z3 = z2 + 3
in foldl (+) z3 [4..1000000] -->
let z1 = 0 + 1
z2 = z1 + 2
z3 = z2 + 3
z4 = z3 + 4
in foldl (+) z4 [5..1000000] -->
However, I have trouble on larger equations understanding why things work the way they do in Haskell. For example the first sieve function uses 1000 filters while the second sieve function takes only 24 to find the 1001 prime.
primes = sieve [2..]
where
sieve (p:xs) = p : sieve [x | x <- xs, rem x p /= 0]
primes = 2: 3: sieve (tail primes) [5,7..]
where
sieve (p:ps) xs = h ++ sieve ps [x | x <- t, rem x p /= 0]
-- or: filter ((/=0).(`rem`p)) t
where (h,~(_:t)) = span (< p*p) xs
I have spent a good while working out and expanding by hand. I have come to understand how it works. However, an automated tool to expand certain expressions would greatly improve my understanding of Haskell.
In addition I think it could also serve to help questions that seek to optimize Haskell code:
- Optimizing Haskell Code
- Help optimize my haskell code - Calculate the sum of all the primes below two million
Is there a tool to expand Haskell expressions?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
大卫·V. 感谢您提供这些链接。
Repr
绝对值得添加到我的工具箱中。我想添加一些我认为有用的额外库。HackageDB:跟踪 (截至 2010 年 12 月 12 日)
Hook包似乎就是我正在寻找的。今天晚些时候我将发布更多示例。
Hood
输出
我不知道黑客攻击图书馆(因为我刚刚进入 Haskell)。这让我想起了 Perl 的 CPAN。感谢您提供这些链接。这是一个很好的资源。
David V. Thank you for those links.
Repr
is definitely worth adding to my tool box. I would like to add some additional libraries that I found useful.HackageDB : Trace (As of December 12, 2010)
The Hook package seems to be what I am looking for. I will post more samples later today.
Hood
outputs
I was unaware of the Hackage library (as I am just getting into Haskell). It reminds me of Perl's CPAN. Thank you for providing those links. This is a great resource.
这绝不是对您问题的完整答复,但我在 Haskell-Cafe 上发现了一个对话,其中有一些答复:
http://www.haskell.org/pipermail/haskell-cafe/2010-June/078763.html
该线程链接到此包:
http://hackage.haskell.org/package/repr 根据页面“允许您渲染重载表达式到其文本表示”
提供的示例是:
This is in no way a full reply to your question, but I found a conversation on Haskell-Cafe that have some replies :
http://www.haskell.org/pipermail/haskell-cafe/2010-June/078763.html
That thread links to this package :
http://hackage.haskell.org/package/repr that according to the page "allows you to render overloaded expressions to their textual representation"
The example supplied is :
这是对一个未提出的问题的回答,将其视为一条长评论。
(请仅在低于 0 的情况下投反对票,如果您认为它不合适。那么我会将其删除。
)一旦你更有经验了,你可能就不想再看到事情扩展的方式了。你会想要了解事情是如何运作的,然后取代“为什么它运作”的问题;仅仅通过观察它如何扩展你不会再获得太多收获。
分析代码的方法比您想象的要简单得多:只需将每个参数/变量标记为“已评估”或“未评估”或“待评估”,具体取决于它们因果关系的进展。
两个例子:
1.) fibs
所有斐波那契数的列表是
前两个元素已经被评估;因此,将第三个元素(值为 2)标记为待评估,并将所有剩余元素标记为未评估。第三个元素将是 fibs 的第一个元素和尾部 fibs 的 (+) 组合,这将是 fibs 的第一个和第二个元素,它们已经被标记为已评估。这分别适用于要评估的第 n 个元素以及第 (n-2) 个和第 (n-1) 个已评估的元素。
您可以用不同的方式来可视化这一点,即:
2.) 您的示例“sieve (p:ps) xs”
在“sieve (p:ps) xs”中,
Sieve 应该返回 p 之后的素数列表,因此至少要评估下一个素数。
函数定义的其余部分是递归,其中下一个 xs 是 t,其中所有 n*p 都被筛掉。
对于foldr,分析会发现“go”只是为了加速运行时间而定义的,而不是为了提高可读性。这是一个更容易分析的替代定义:
我已经描述了它的功能 此处(未经分析)。
要训练这种类型的分析,您可能需要阅读一些标准库的源代码;即 来源 数据.List.
This is an answer to an unasked question, think of it as a long comment.
(Please downvote only then below 0, iff you think that it does not fit. I'll remove it then.)
As soon as you are a bit more experienced, you might not want to see the way things expand, anymore. You'll want to understand HOW things work, which then supersedes the question WHY it works; you won't gain much just by observing how it expands, anymore.
The way to analyse the code is much simpler than you might think: Just label every parameter/variable either as "evaluated" or "unevaluated" or "to-be-evaluated", depending on the progression of their causal connections.
Two examples:
1.) fibs
The list of all Fibonacci Numbers is
The first two elements are already evaluated; so, label the 3rd element (which has value 2) as to-be-evaluated and all remaining as unevaluated. The 3rd element will then be the (+)-combination of the first elements of fibs and tail fibs, which will be the 1st and 2nd element of fibs, which are already labelled as evaluated. This works with the n-th element to-be-evaluated and the (n-2)-nd and (n-1)-st already evaluated elements respectively.
You can visualize this in different ways, i.e.:
2.) Your example "sieve (p:ps) xs"
In "sieve (p:ps) xs",
Sieve should return the list of primes after p, so at least the next prime is to-be-evaluated.
The rest of the function definition is the recursion, where the next xs is t with all n*p sieved out.
In the case of foldr, an analysis will show that the "go" is only defined to speed up the runtime, not the readability. Here is an alternative definition, that is easier to analyse:
I've described its functionality here (without analysis).
To train this type of analysing, you might want to read the sources of some standard libraries; i.e. scanl, scanr, unfoldr in the source of Data.List.