我对二郎语的了解越来越多,最近也遇到了一些问题。我读过关于foldl(Fun, Acc0, List) -> Acc1函数的文章。我使用了learnyousomeerlang.com教程,并有一个例子(例如Erlang中的反向波兰符号计算器):
%function that deletes all whitspaces and also execute
rpn(L) when is_list(L) ->
[Res] = lists:foldl(fun rpn/2, [], string:tokens(L," ")),
Res.
%function that converts string to integer or floating poitn value
read(N) ->
case string:to_float(N) of
%returning {error, no_float} where there is no float avaiable
{error,no_float} -> list_to_integer(N);
{F,_} -> F
end.
%rpn managing all actions
rpn("+",[N1,N2|S]) -> [N2+N1|S];
rpn("-", [N1,N2|S]) -> [N2-N1|S];
rpn("*", [N1,N2|S]) -> [N2*N1|S];
rpn("/", [N1,N2|S]) -> [N2/N1|S];
rpn("^", [N1,N2|S]) -> [math:pow(N2,N1)|S];
rpn("ln", [N|S]) -> [math:log(N)|S];
rpn("log10", [N|S]) -> [math:log10(N)|S];
rpn(X, Stack) -> [read(X) | Stack].据我所知,lists:foldl对列表中的每个元素都执行rpn/2。但这是我所能理解的功能。我读了这些文件,但对我没有多大帮助。有人能解释一下lists:foldl是如何工作的吗?
发布于 2014-11-10 23:39:39
假设我们要将数字列表加在一起:
1 + 2 + 3 + 4.这是一种非常正常的写作方式。但我写的是“把数字加在一起”,而不是“在数字之间加上加号”。我用散文表达运算的方式和我使用的数学表示法之间有着根本的不同。我们这样做是因为我们知道它是一个相等的加法符号(因为它是可交换的),而且在我们的头脑中,它立即减少到:
3 + 7.然后
10.那有什么大不了的?问题是我们无法从这个例子中理解求和的概念。如果我写了“从0开始,然后每次从列表中取出一个元素,并将其作为运行的和添加到起始值”,那该怎么办?这实际上就是求和的意义,在方程被简化之前,它并不是任意地决定先添加哪两个东西。
sum(List) -> sum(List, 0).
sum([], A) -> A;
sum([H|T], A) -> sum(T, H + A).如果你现在和我在一起,那么你就可以理解褶皱了。
上面的函数有一个问题,它太具体了。它将三个想法编织在一起,而没有独立地说明:
很容易忽略迭代和积累之间的区别,因为大多数时候我们从未考虑过这一点。实际上,通过让相同的存储位置在类似函数的每一次迭代中更改其值,大多数语言都会意外地鼓励我们忽略这种差异。
很容易忽略加法的独立性,仅仅因为它在本例中的编写方式,因为"+“看起来是一个”操作“,而不是一个函数。
如果我说“从1开始,然后每次从列表中取一个元素并乘以它的运行值”,该怎么办?我们仍然会以完全相同的方式处理列表,但是用两个例子进行比较很明显,乘法和加法是两者之间的唯一区别:
prod(List) -> prod(List, 1).
prod([], A) -> A;
prod([H|T], A) -> prod(T, H * A).这是完全相同的执行流程,但是对于内部操作和累加器的起始值。
因此,让我们把加法和乘法位转换成函数,这样我们就可以将模式的这一部分提取出来:
add(A, B) -> A + B.
mult(A, B) -> A * B.我们如何单独编写列表操作?我们需要传递一个函数--加法或乘法--并让它对值进行运算。此外,我们必须注意我们正在操作的事物的类型和操作的身份,否则我们会搞砸魔术,即价值聚合。"add (0,X)“总是返回X,所以这个想法(0+ Foo)是加法标识操作。在乘法中,恒等运算是乘以1。因此,我们必须在0开始累加器,1表示乘法(以及建立列表,空列表,等等)。因此,我们不能用内置的累加器值来编写函数,因为它只对某些type+operation对是正确的。
因此,这意味着编写一个折叠,我们需要一个列表参数,一个函数来做事情参数,和一个累加器参数,如下所示:
fold([], _, Accumulator) ->
Accumulator;
fold([H|T], Operation, Accumulator) ->
fold(T, Operation, Operation(H, Accumulator)).有了这个定义,我们现在可以使用更一般的模式编写sum/1:
fsum(List) -> fold(List, fun add/2, 0).和prod/1还:
fprod(List) -> fold(List, fun prod/2, 1).它们在功能上和我们上面写的一样,但是表示法更清楚,我们不需要写一些递归的细节,把迭代的想法和累加的想法和一些特定的运算(如乘法或加法)的想法结合在一起。
在RPN计算器的情况下,聚合列表操作的思想与选择性分派的概念相结合(根据遇到/匹配的符号选择一个操作来执行)。RPN示例相对简单且较小(您可以一次将所有代码放入头脑中,只需几行代码),但除非您习惯了功能范式,否则它所显示的过程可能会使您头疼。在函数式编程中,少量的代码可以创建一个任意复杂的不可预知的过程(甚至是进化过程!)行为,仅基于列表操作和选择性分派;这与目前更常见的其他范例中使用的条件检查、输入验证和过程检查技术有很大不同。通过单次赋值和递归表示法可以很好地帮助分析这种行为,因为每一次迭代都是一个独立于概念上的时间片段,可以与系统的其他部分分开来考虑。我说的是一些基本的问题,但这是一个核心的想法,你可能想要思考,因为你考虑为什么我们喜欢使用像折叠和递归符号,而不是过程,多分配循环。
我希望这能帮到更多的忙。
发布于 2014-11-11 08:29:15
首先,你必须记住山楂作品rpn。如果您想执行以下操作:2 * (3 + 5),您将使用输入:"3 5 + 2 *"为函数提供信息。在您有25步输入程序的时候,这是非常有用的:o)
第一个函数简单地将此字符列表拆分为元素:
1> string:tokens("3 5 + 2 *"," ").
["3","5","+","2","*"]
2>然后处理列表:foldl/3。对于该列表的每个元素,使用输入列表和当前累加器的头调用rpn/2,并返回一个新的累加器。让我们一步一步地走:
Step head accumulator matched rpn/2 return value
1 "3" [] rpn(X, Stack) -> [read(X) | Stack]. [3]
2 "5" [3] rpn(X, Stack) -> [read(X) | Stack]. [5,3]
3 "+" [5,3] rpn("+", [N1,N2|S]) -> [N2+N1|S]; [8]
4 "2" [8] rpn(X, Stack) -> [read(X) | Stack]. [2,8]
5 "*" [2,8] rpn("*",[N1,N2|S]) -> [N2*N1|S]; [16]最后,lists:foldl/3返回与[R]匹配的[16],尽管rpn/1返回R = 16
https://stackoverflow.com/questions/26854586
复制相似问题