这段代码对C的翻译是什么,更确切地说是'foldl'的作用是什么?
我认为这段代码可能是Haskell,但我不太确定。
foldl (\x y -> y:x) [] [1..42]
答案 0 :(得分:9)
将此短行转换为C将是一项相当大的工作,因为它需要定义链接列表并使用指针。相反,我只会尝试用foldl
来解释。
Haskell中的foldl
函数的类型为foldl :: (a -> b -> a) -> a -> [b] -> a
。这里,a
和b
是类型变量,可以是任何类型,只要您保持一致。让我们专注于您正在处理的问题。首先,我们看到传递给foldl
的列表是[1..42]
,其类型为[Int]
。这与foldl
参数一致[b]
,因此[b] ~ [Int]
(~
是类型相等符号),我们可以推导出b ~ Int
。传递给foldl
的第二个值是[]
,在这种情况下,其类型为[Int]
,因此a ~ [Int]
。如果我们将这些插回到完整类型签名中,我们就会得到
foldl :: ([Int] -> Int -> [Int]) -> [Int] -> [Int] -> [Int]
那么lambda函数(\x y -> y : x)
呢?它所做的就是获取一个列表,一个元素,并将该元素添加到列表的前面。一个例子是
> let f = (\x y -> y : x)
> f [1, 2, 3] 0
[0, 1, 2, 3]
> f [] 1
[1]
> f (f [1, 2] 3) 4
[4, 3, 1, 2]
foldl
对该函数的作用是重复调用它,从列表中提取值。 []
作为第二个参数是初始值。所以在一个简短的清单上,它看起来像
foldl (\x y -> y : x) [] [1, 2, 3, 4]
(\x y -> y : x) [] 1 = 1 : [] = [1]
foldl (\x y -> y : x) [1] [2, 3, 4]
(\x y -> y : x) [1] 2 = 2 : [1] = [2, 1]
foldl (\x y -> y : x) [2, 1] [3, 4]
(\x y -> y : x) [2, 1] 3 = 3 : [2, 1] = [3, 2, 1]
foldl (\x y -> y : x) [3, 2, 1] [4]
(\x y -> y : x) [3, 2, 1] 4 = 4 : [3, 2, 1] = [4, 3, 2, 1]
foldl (\x y -> y : x) [4, 3, 2, 1] [] = [4, 3, 2, 1]
因此,此特定折叠会反转列表。
C中的一个实现只专门用于int
链接列表(没有多态),没有懒惰,也没有不变性
#include <stdlib.h>
#include <stdio.h>
struct Node_
{
int val;
struct Node_ *next;
};
typedef struct Node_ Node;
typedef struct Node_ * NodePtr;
NodePtr mkNode(int val) {
NodePtr n = (NodePtr) malloc(sizeof(Node));
n->val = val;
n->next = NULL;
return n;
}
NodePtr reverse(NodePtr head) {
NodePtr current = head;
NodePtr newHead = mkNode(head->val);
NodePtr temp;
while (current->next != NULL) {
temp = mkNode(current->next->val);
current = current->next;
temp->next = newHead;
newHead = temp;
}
return newHead;
}
NodePtr fromTo(int start, int stop) {
NodePtr root = mkNode(start);
NodePtr conductor = root;
NodePtr temp;
while (++start <= stop) {
temp = mkNode(start);
conductor->next = temp;
conductor = conductor->next;
}
return root;
}
void printNode(NodePtr root) {
NodePtr copy = root;
while (copy->next != NULL) {
printf("%d ", copy->val);
copy = copy->next;
}
printf("%d\n", copy->val);
}
int main(int argc, char const *argv[])
{
NodePtr numbers = fromTo(1, 10);
printNode(numbers);
printNode(reverse(numbers));
return 0;
}
实际执行的时间约为30行,功能示例为60行。如你所见,Haskell比C更具表现力。
您甚至可以在C中编写foldl
的专用版本,并使用它实现reverse
:
NodePtr foldl_NodePtr(NodePtr (*func)(NodePtr, int), NodePtr initial, NodePtr root) {
NodePtr val = initial;
NodePtr copy = root;
while (copy->next != NULL) {
val = func(val, copy->val);
copy = copy->next;
}
val = func(val, copy->val);
return val;
}
NodePtr lambda(NodePtr node, int val) {
NodePtr temp = mkNode(val);
temp->next = node;
return temp;
}
NodePtr reverse_foldl(NodePtr root) {
NodePtr temp = mkNode(root->val);
return foldl_NodePtr(lambda, temp, root->next);
}
如果你想用C中的折叠实现sum
int foldl_int(int (*func)(int, int), int initial, NodePtr root) {
int val = initial;
NodePtr copy = root;
while (copy->next != NULL) {
val = func(val, copy->val);
copy = copy->next;
}
val = func(val, copy->val);
return val;
}
int add(int x, int y) { return x + y; }
int sum(NodePtr root) { return foldl_int(add, 0, root); }
令人惊讶的简洁。
如果你错过了这个细节,在reverse_foldl
我们必须使初始值成为已经填充的节点,因为链接列表的这个定义不支持制作空列表,相当于[]
。相反,我们创建第一个节点,然后将root->next
传递给foldl
。
答案 1 :(得分:0)
你不需要C来理解Haskell。使用某个中缀运算符可以很容易地显示foldl
的操作,我们称之为*
:
foldl (*) z [a,b,c,...,n] == (... (((z * a) * b) * c) * ...) * n
您的函数(\x y -> y:x)
是一个缺点(:)
,其参数被翻转:flip (:) a b = b:a
。因此,foldl
来电变为
= n : (... : (c : (b : (a : []))) ...)
这只是输入列表[n,...,c,b,a]
的反转,正如另一个答案中所述。
还有一个正确的折叠,foldr
:
foldr (*) z [a,b,c,...,n] == a * (b * (c * (... * (n * z) ...)))
尝试
> foldr (:) [] [1..42]
> let xs = foldr (:) xs [1..42] in take 50 xs
> let ys = foldl (flip (:)) ys [1..42] in take 50 $ drop 40 ys
等
但如果你“必须”用C语言写,那么首先要翻译输入列表[1..42]
,例如。
// [1..42]
int a[42];
int i=0;
int n=1;
for( ; i<42; ++i)
{
a[i] = n;
n += 1;
}
int *p1 = &a[0];
int incr_1 = 1;
然后翻译foldl
电话:
// foldl (\x y -> y:x) [] [1..42]
int *p2 = &a[41];
int incr_2 = -1;
这是对的,实际上是 no-op 。由于Haskell的纯度(不变性),可以使用相同的C对象来表示多个Haskell对象。只需要调整寻址方案。相同的数组a[42]
甚至可以用来表示循环列表或它们的任何部分。