是否可以在独立 C 中实现 Python yield 功能?
Is it possible to implement Python yield functionality in freestanding C?
我最近在 Python(以及 JavaScript)中遇到了 yield
关键字 - 我知道这主要用于生成器模式,但语言构造似乎也用于异步函数以及我感兴趣的地方。在异步函数中,它可能仅充当语法糖,我知道有其他模式可以达到相同的效果——但我喜欢它——非常喜欢!
我想知道我是否可以在 C 中做类似的事情(即使使用内联汇编)。我遇到了一个使用线程 https://github.com/mherrmann/java-generator-functions 的 Java 实现,我可以或多或少地在 C 中实现它。但这不是一个独立的实现,我的兴趣纯粹是一个独立的实现。
来到 C 协同例程 (http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html),缺陷之一是无法使用堆栈对象。但是我对此仍然没问题,因为当前的异步回调实现也不能使用堆栈。然而,问题在于一个独立的实现——我想不出一种方法来收集所有的寄存器变量并在没有托管环境的情况下存储它们。
可能有一个使用 setjmp/longjmp
的解决方案,但我很确定这些不能独立实施。
所以问题是:是否可以在 freestanding C 中实现 Python yield 功能?
就我个人而言,我认为我已经用尽了所有可能性,所以我会问这个问题 - 如果您可以进行托管实施,您将如何实施(最好使用一些宏魔法)?我有一个相当难看的实现,如果没有什么好东西的话,我稍后会 post。
我也不想要 C++ 实现 - 除非你可以用纯 C 函数环绕 C++。
编辑:一个基本要求是生成器函数必须是可重入的。
我将使用 setjmp
和 longjmp
来回答,因为这些接口是标准的,您可以轻松找到它们在任何硬件平台上的实现。它们是独立的,但依赖于硬件。
struct _yield_state {
jmp_buf buf;
_Bool yielded;
};
#define yieldable static struct _yield_state _state; \
if (_state.yielded) longjmp(_state.buf, 1); else {}
#define yield(x) if (setjmp(_state.buf)) { _state.yielded = false; }\
else { _state.yielded = true; return x }
int func(int a, int b)
{
yieldable;
if (a > b)
yield(0);
return a + b;
}
您可以找到示例 setjmp
和 longjmp
实现 here。它是纯汇编,仅特定于底层硬件。
Python 中的迭代器遵循以下模式:您调用它们(带参数)并且它们 return 一个对象。您重复调用该对象的 .next()
或 .__next__()
方法,它会在迭代器中运行。
我们可以做类似的事情:
typedef struct iterator{
int yield_position; /* Where to jump to */
void *yield_state; /* opaque container for local variables */
void *(*next)(iterator*); /* Function taking "this" argument
returning a pointer to whatever we yielded */
} iterator;
iterator *make_generator(/* arguments? */){
iterator *result = malloc(sizeof(iterator)); /* Caller frees */
result->yield_position = 0;
/* Optionally allocate/initialize yield_state here */
result->next = do_generator;
return result;
}
void *do_generator(iterator *this){
struct whatever *result;
switch(this->yield_position){
case 0:
/* Do something */
this->yield_position = 1;
/* Save local variables to this->yield_state if necessary */
return (void *) result;
case 1:
/* Initialize local variables from this->yield_state */
/* Etc.*/
}
}
void free_generator(iterator *iter){
/* Free iter->yield_state if necessary */
free(iter);
}
由于案例标签 can be used just about everywhere,开关应该能够,例如如有必要,跳入循环的中间。您可能仍需要重新初始化循环变量等。
它的名字是这样的:
iterator *iter = make_generator(/* arguments? */);
struct whatever *foo = iter->next(iter);
/* etc. */
free_generator(iter);
手动传递 this
参数很乏味,所以定义一个宏:
#DEFINE NEXT(iter) ((iter)->next(iter))
忽略特定语言的行话,您要找的是 "coroutines"。 Simon Tatham 想出了一些看起来和行为都非常像带有一些预处理器魔法的协程的东西。它并不完全以相同的方式工作,但它假装以一种对大多数情况有用的方式。
有关完整详细信息,请参阅 here。
根据您的具体问题,这可能就足够了,也可能不够。无论如何,这种方法的优点是它适用于标准 C;不需要非标准编译器。
我最近在 Python(以及 JavaScript)中遇到了 yield
关键字 - 我知道这主要用于生成器模式,但语言构造似乎也用于异步函数以及我感兴趣的地方。在异步函数中,它可能仅充当语法糖,我知道有其他模式可以达到相同的效果——但我喜欢它——非常喜欢!
我想知道我是否可以在 C 中做类似的事情(即使使用内联汇编)。我遇到了一个使用线程 https://github.com/mherrmann/java-generator-functions 的 Java 实现,我可以或多或少地在 C 中实现它。但这不是一个独立的实现,我的兴趣纯粹是一个独立的实现。
来到 C 协同例程 (http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html),缺陷之一是无法使用堆栈对象。但是我对此仍然没问题,因为当前的异步回调实现也不能使用堆栈。然而,问题在于一个独立的实现——我想不出一种方法来收集所有的寄存器变量并在没有托管环境的情况下存储它们。
可能有一个使用 setjmp/longjmp
的解决方案,但我很确定这些不能独立实施。
所以问题是:是否可以在 freestanding C 中实现 Python yield 功能?
就我个人而言,我认为我已经用尽了所有可能性,所以我会问这个问题 - 如果您可以进行托管实施,您将如何实施(最好使用一些宏魔法)?我有一个相当难看的实现,如果没有什么好东西的话,我稍后会 post。
我也不想要 C++ 实现 - 除非你可以用纯 C 函数环绕 C++。
编辑:一个基本要求是生成器函数必须是可重入的。
我将使用 setjmp
和 longjmp
来回答,因为这些接口是标准的,您可以轻松找到它们在任何硬件平台上的实现。它们是独立的,但依赖于硬件。
struct _yield_state {
jmp_buf buf;
_Bool yielded;
};
#define yieldable static struct _yield_state _state; \
if (_state.yielded) longjmp(_state.buf, 1); else {}
#define yield(x) if (setjmp(_state.buf)) { _state.yielded = false; }\
else { _state.yielded = true; return x }
int func(int a, int b)
{
yieldable;
if (a > b)
yield(0);
return a + b;
}
您可以找到示例 setjmp
和 longjmp
实现 here。它是纯汇编,仅特定于底层硬件。
Python 中的迭代器遵循以下模式:您调用它们(带参数)并且它们 return 一个对象。您重复调用该对象的 .next()
或 .__next__()
方法,它会在迭代器中运行。
我们可以做类似的事情:
typedef struct iterator{
int yield_position; /* Where to jump to */
void *yield_state; /* opaque container for local variables */
void *(*next)(iterator*); /* Function taking "this" argument
returning a pointer to whatever we yielded */
} iterator;
iterator *make_generator(/* arguments? */){
iterator *result = malloc(sizeof(iterator)); /* Caller frees */
result->yield_position = 0;
/* Optionally allocate/initialize yield_state here */
result->next = do_generator;
return result;
}
void *do_generator(iterator *this){
struct whatever *result;
switch(this->yield_position){
case 0:
/* Do something */
this->yield_position = 1;
/* Save local variables to this->yield_state if necessary */
return (void *) result;
case 1:
/* Initialize local variables from this->yield_state */
/* Etc.*/
}
}
void free_generator(iterator *iter){
/* Free iter->yield_state if necessary */
free(iter);
}
由于案例标签 can be used just about everywhere,开关应该能够,例如如有必要,跳入循环的中间。您可能仍需要重新初始化循环变量等。
它的名字是这样的:
iterator *iter = make_generator(/* arguments? */);
struct whatever *foo = iter->next(iter);
/* etc. */
free_generator(iter);
手动传递 this
参数很乏味,所以定义一个宏:
#DEFINE NEXT(iter) ((iter)->next(iter))
忽略特定语言的行话,您要找的是 "coroutines"。 Simon Tatham 想出了一些看起来和行为都非常像带有一些预处理器魔法的协程的东西。它并不完全以相同的方式工作,但它假装以一种对大多数情况有用的方式。
有关完整详细信息,请参阅 here。
根据您的具体问题,这可能就足够了,也可能不够。无论如何,这种方法的优点是它适用于标准 C;不需要非标准编译器。