将Haskell行转换为C.

时间:2013-12-09 18:16:41

标签: c haskell

这段代码对C的翻译是什么,更确切地说是'foldl'的作用是什么?

我认为这段代码可能是Haskell,但我不太确定。

foldl (\x y -> y:x) [] [1..42]

2 个答案:

答案 0 :(得分:9)

将此短行转换为C将是一项相当大的工作,因为它需要定义链接列表并使用指针。相反,我只会尝试用foldl来解释。

Haskell中的foldl函数的类型为foldl :: (a -> b -> a) -> a -> [b] -> a。这里,ab是类型变量,可以是任何类型,只要您保持一致。让我们专注于您正在处理的问题。首先,我们看到传递给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]甚至可以用来表示循环列表或它们的任何部分。