如何在C中实现Lazy Evaluation?

时间:2009-10-28 08:22:44

标签: python c

举个例子,

以下python代码:

def multiples_of_2():
  i = 0
  while True:
    i = i + 2
    yield i

我们如何将其转换为C代码?

编辑:我希望将这个python代码转换为C语言中的类似生成器,并使用next()函数。我不想要的是如何在C中创建一个函数来输出2的倍数.2的倍数仅仅是一个例子来说明C中懒惰的eval生成器的问题。

9 个答案:

答案 0 :(得分:20)

您可以尝试将其封装在struct

typedef struct s_generator {
    int current;
    int (*func)(int);
} generator;

int next(generator* gen) {
    int result = gen->current;
    gen->current = (gen->func)(gen->current);
    return result;
}

然后用以下代码定义倍数:

int next_multiple(int current) { return 2 + current; }
generator multiples_of_2 = {0, next_multiple};

通过调用

获得下一个倍数
next(&multiples_of_2);

答案 1 :(得分:5)

我最近在coroutines in C上发现了一篇很好的文章,其中描述了一种方法。这当然不适合胆小的人。

答案 2 :(得分:2)

如前所述,像python这样的语言可以在生成器的连续调用之间存储堆栈状态。由于C没有这种机制,你必须自己做。正如格雷格所指出的那样,这种做法的“通用”方式不适合胆小的人。传统的C方式是您自己定义和维护状态并将其传入和传出方法。所以:

struct multiples_of_two_state {
       int i;
       /* all the state you need should go in here */
};

void multiples_of_two_init(struct multiples_of_two_state *s) {
    s->i = 0;
}

int multiples_of_two_next(struct multiples_of_two_state *s) {
    s->i += 2;
    return s->i;
}

/* Usage */
struct multiples_of_two_state s;
int result;
multiples_of_two_init(&s);
for (int i=0; i<INFINITY; i++) {
    result = multiples_of_two_next(&s);
    printf("next is %d", result);
}

答案 3 :(得分:1)

基本方法是不要这样做。在Python(和C#)中,'yield'方法在调用之间存储本地状态,而在C / C ++和大多数其他语言中,存储在堆栈上的本地状态不会在调用之间保留,这是一个有趣的实现差异。因此,在C中,您必须在某些变量中明确地存储调用之间的状态 - 全局变量或函数参数到序列生成器。所以:

int multiples_of_2() {
   static int i = 0;
   i += 2;
   return i;
}

int multiples_of_2(int i) {
   i += 2;
   return i;
}

取决于是否有一个全局序列或多个。

我很快就考虑过longjmp和GCC计算得到的和其他非标准的东西,我不能说我会推荐其中任何一个!在C中,用C语言做。

答案 4 :(得分:1)

查看setjmp/longjmp

  

setjmp.h是C中定义的头   标准库提供“非本地   跳跃,“或控制流动除了   通常的子程序调用和返回   序列。配对函数setjmp   和longjmp提供这个   功能。第一个setjmp保存了   调用函数的环境   进入数据结构,然后   longjmp可以使用这个结构   “跳”回到原点   在setjmp调用中创建。

Lua coroutines以这种方式实施)

答案 5 :(得分:1)

您可以将参数作为指针传递,以允许函数在不使用返回值的情况下修改它:

void multiples_of_2(int *i)
{
    *i += 2;
}

并称之为:

int i = 0;
multiples_of_2(&i);

答案 6 :(得分:1)

关键是保持调用之间的函数状态。您有很多选择:

  1. 静态(或全局)状态。意味着对函数的调用序列不是可重入的,即你不能递归地调用函数本身,也不能让多个调用者运行不同的调用序列。

  2. 在第一次通话时或之前初始化(并可能分配)状态,并在每次后续通话时将其传递给该功能。

  3. 使用setjmp / longjmp,堆栈或可修改的代码做一些聪明的事情(有一篇关于在C中调整函数的文章,它创建了一个带有必要代码的对象来调用curried函数;类似的技术可以创建一个具有函数状态的对象以及为每次调用保存和恢复它的必要代码。 (修改找到它 - http://asg.unige.ch/site/papers/Dami91a.pdf

  4. Greg引用了一篇有趣的文章,它提供了一种使用静态语法的方法,其语法类似于yield语句。我喜欢它在学术上,但可能不会因为重入问题而使用它,因为我仍然感到惊讶,臭名昭着的Duffy的设备甚至编译;-)。

    在实践中,大型C程序确实想要懒惰地计算事物,例如数据库服务器可能希望通过将普通SELECT ... LIMIT 10查询包含在将产生每行直到返回10的内容中来满足SELECT查询,而不是计算整个结果然后丢弃大部分结果。最像C的技术是为状态显式创建一个对象,并为每个调用调用一个函数。对于您的示例,您可能会看到类似的内容:

    /* Definitions in a library somewhere. */
    typedef int M2_STATE;
    M2_STATE m2_new() { return 0; }
    int m2_empty(M2_STATE s) { return s < INT_MAX; }
    int m2_next(M2_STATE s) { int orig_s = s; s = s + 2; return orig_s; }
    
    /* Caller. */
    M2_STATE s;
    s = m2_new();
    while (!m2_empty(s))
    {
        int num = m2_next(s);
        printf("%d\n", num);
    }
    

    这对于2的倍数来说似乎很麻烦,但它对于更复杂的生成器来说变得很有用。您可以使状态更复杂,而不必将使用您的生成器的所有代码加载到详细信息中。更好的做法是在new函数中返回一个不透明指针,并且(除非GC可用)提供清理生成器的功能。

    为每个新的调用序列分配状态的最大好处是像递归生成器这样的东西。例如,通过在每个子目录上调用自身来返回目录下的所有文件的生成器。

    char *walk_next(WALK_STATE *s)
    {
        if (s->subgenerator)
        {
            if (walk_is_empty(s->subgenerator))
            {
                walk_finish(s->subgenerator);
                s->subgenerator = NULL;
            }
            else
                return walk_next(s->subgenerator);
        }
    
        char *name = readdir(s->dir);
        if (is_file(name))
            return name;
        else if (is_dir(name))
        {
            char subpath[MAX_PATH];
            strcpy(subpath, s->path);
            strcat(subpath, name);
            s->subgenerator = walk_new(subpath);
            return walk_next(s->subgenerator);
        }
        closedir(s->dir);
        s->empty = 1;
        return NULL;
    }
    

    (你必须原谅我滥用readdir等等,以及我假装C有傻逼的字符串支持。)

答案 7 :(得分:0)

int multiples_of_2() {
    static int i = 0;
    i += 2;
    return i;
}

static int i的行为类似于全局变量,但仅在multiples_of_2()的上下文中可见。

答案 8 :(得分:0)

我已经实施了自己的懒惰评估,以解决汉明的问题。

对于任何有兴趣的人来说,这是我的代码:

#include <stdio.h>
#include <stdlib.h>

// Hamming problem in C.

typedef struct gen {
  int tracker;
  int (*next)(struct gen *g);
} generator;

int twos_gen(struct gen *g) {
  g->tracker = g->tracker + 2;
  return g->tracker;
}

generator* twos_stream() {
  generator *g = malloc(sizeof(generator));
  g->next = twos_gen;
  g->tracker = 0;
  return g;
}

int threes_gen(struct gen *g) {
  g->tracker = g->tracker + 3;
  return g->tracker;
}

generator* threes_stream() {
  generator *g = malloc(sizeof(generator));
  g->next = threes_gen;
  g->tracker = 0;
  return g;
}

int fives_gen(struct gen *g) {
  g->tracker = g->tracker + 5;
  return g->tracker;
}

generator* fives_stream() {
  generator *g = malloc(sizeof(generator));
  g->next = fives_gen;
  g->tracker = 0;
  return g;
}

int smallest(int a, int b, int c) {
  if (a < b) {
    if (c < a) return c;
    return a;
  }
  else {
    if (c < b) return c;
    return b;
  }
}

int hamming_gen(struct gen *g) {
  generator* twos = twos_stream();
  generator* threes = threes_stream();
  generator* fives = fives_stream();

  int c2 = twos->next(twos);
  int c3 = threes->next(threes);
  int c5 = fives->next(fives);

  while (c2 <= g->tracker) c2 = twos->next(twos);
  while (c3 <= g->tracker) c3 = threes->next(threes);
  while (c5 <= g->tracker) c5 = fives->next(fives);

  g->tracker = smallest(c2,c3,c5);
  return g->tracker;
}

generator* hammings_stream() {
  generator *g = malloc(sizeof(generator));
  g->next = hamming_gen;
  g->tracker = 0;
  return g;
}

int main() {
  generator* hammings = hammings_stream();
  int i = 0;
  while (i<10) {
    printf("Hamming No: %d\n",hammings->next(hammings));
    i++;
  }
}