2015-05-22 2 views
13

Недавно я пришел через ключевое слово yield в Python (а также JavaScript). Я понимаю, что это primarliy используется для шаблона генератора, но конструкция языка, похоже, используется в асинхронных функциях, а также где мои интересы лежат. В асинхронных функциях он может просто действовать как синтаксический сахар, и я знаю, что существуют альтернативные шаблоны для достижения того же эффекта. Но мне это нравится - ЛЕТ!Возможно ли реализовать функциональность выхода Python в автономном режиме C?

Я хочу знать, могу ли я сделать что-то подобное в C (даже с встроенной сборкой). Я наткнулся на реализацию Java, используя потоки https://github.com/mherrmann/java-generator-functions, которые я могу более или менее реализовать в C. Однако это не будет самостоятельной реализацией, и мой интерес заключается исключительно в самостоятельной реализации.

Входящие в C совместные подпрограммы (http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html), один из недостатков заключается в том, что объекты стека не могут использоваться. Однако я все еще в порядке, поскольку текущие асинхронные реализации обратного вызова также не могут использовать стек. Однако проблема заключается в самостоятельной реализации - я не могу придумать способ сбора всех переменных регистра и сохранения их без размещенной среды.

Возможно, существует решение с использованием setjmp/longjmp, однако я уверен, что они не могут быть реализованы отдельно.

Вопрос: Можно ли реализовать функциональность выхода Python в отдельностоящий C?

Лично я думаю, что исчерпал возможности, поэтому я попрошу об этом. Если бы у вас была размещенная реализация, как бы вы ее реализовали (желательно с помощью некоторой макромагии)? У меня довольно уродливая реализация, которую я выложу позже, если ничего не придет.

Также я не хочу реализаций на C++ - если вы не можете обернуть C++ чистыми функциями C.

РЕДАКТИРОВАНИЕ: Основное требование состоит в том, чтобы функция генератора должна была повторно вводиться.

+0

Учитывая, что интерпретатор Python написан на языке C ... да? – chepner

+1

Я так не думаю. Вам либо нужна стандартная библиотека (которая реализует 'setjmp' и' longjmp'), либо вам нужно выполнить некоторые конкретные операции с оборудованием, наиболее вероятно связанные с сборкой. Просто, как вам это нужно? – mtijanic

+0

@mtijanic Специальные операции оборудования - это хорошо. Я хочу - если бы я мог модифицировать компилятор С и ввести синтаксис урока, тогда этот синтаксис должен быть доступен на первом этапе компиляции компилятора (когда он может скомпилировать только автономный код). Он не должен зависеть от каких-либо конкретных функций ОС, например, при создании второй фазы компилятора. Не уверен, что то, что я только что сказал, имеет смысл для любого. – tinkerbeast

ответ

3

Я собираюсь ответить, используя setjmp и longjmp, поскольку эти интерфейсы являются стандартными, и вы можете легко найти их реализации для любой платформы HW. Они независимы, но зависят от HW.

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. Это чистая сборка, специфическая только для основного оборудования.

+0

Поскольку это решение использует статические объекты, функция не является рентабельной. Это приведет к множеству проблем. Тем не менее, я думаю, что вы правы в том, что setjmp и longjmp являются независимыми. Оба LLVM и GCC, похоже, имеют встроенные средства для этих http://llvm.org/docs/ExceptionHandling.html#sjlj-intrinsics – tinkerbeast

+0

@tinkerbeast True. Вы можете изменить его, чтобы принять аргумент состояния от вызывающего, возможно, с чем-то вроде '#define func (a, b) func (a, b, & state)', но тогда бремя на вызывающего абонента выделяет состояние. Другой вариант заключается в том, что статический объект должен быть списком состояний, и вы выбираете его на основе аргумента, переданного вызывающим. Этот аргумент может быть threadId, '__FUNC__' вызывающего абонента или даже случайным числом, сгенерированным в compiletime. – mtijanic

+0

разве вы не думаете, что этот маленький фрагмент кода имеет слишком много способов сломать? – HuStmpHrrr

5

Итераторы в Python следуют этому шаблону: вы вызываете их (с аргументами), и они возвращают объект. Вы повторно вызываете этот объект .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)) 
+0

, если вы освободите 'iter' в примере, который хотите освободить' foo'. кроме того, это не охватывает несколько утверждений выхода в правой части функции (истинная сила сопрограмм)? – Alex

+0

@Alex: Вы * можете * хотите освободить 'foo'. Возможно, вы интернируете свои возвращаемые значения или делаете некоторые другие умные вещи, такие как refcounting. Я намеренно оставил его неопределенным в этом вопросе, чтобы учесть множество вариантов использования. И да, вы получаете несколько операторов доходности. Просто добавьте больше ярлыков кейсов. Обратите внимание, что каждый раз, когда вы хотите уступить, вы должны танцевать все люди, которые спасают и восстанавливают местные жители. – Kevin

+0

Что вы * не * получаете, это 'yield from'. Это еще немного сложно, особенно если вы хотите добавить поддержку '.send()' и т. Д. – Kevin

4

Игнорирование конкретного языка жаргон, что вы ищете называется «сопрограммы». Саймон Татхам придумал что-то, что выглядит и ведет себя очень похоже на сопрограммы с некоторой магией препроцессора. Он не полностью работает так же, но притворяется таким образом, который полезен для большинства случаев.

Для получения более подробной информации см. here.

В зависимости от вашей точной проблемы этого может быть достаточно, или может быть и не так. Преимущество этого метода, во всяком случае, заключается в том, что он работает со стандартом C; нет необходимости в нестандартных компиляторах.

Смежные вопросы