Сопрограммы
От: x-code  
Дата: 03.06.12 17:56
Оценка:
На википедии есть некий пример сопрограмм. Если привести его в более сиподобный вид, то получится примерно следующее:
Queue q(20);
void produce()
{
    for(;;)
    {
        while (!q.IsFull())
        {
            Item item = Gen();
            q.Add(item);
        }
        yield consume;
     }
}

void consume()
{
    for(;;)
    {
        while (!q.IsEmpty())
        {   
            Item item = q.Extract();
            Use(item);
        }
        yield produce;
    }
}


Есть две функции, которые могут где-то сохранять свое состояние и передавать друг другу управление напрямую, без вызова функции. Оператор yield делает нечто, что сохраняет состояние первой функции, переключается на вторую и восстанавливает состояние второй. Желательно, чтобы это нечто было достаточно низкоуровневым и эффективным, с тем чтобы данный оператор можно было применять где угодно, в том числе при программировании embedded софта и т.п. То есть никаких фреймворков нет, есть как-бы "голый си" (некий разрабатываемый си-подобный язык).

Интересно, как можно такую фичу грамотно спроектировать при разработке собственного языка программирования?
Какие требования, ограничения к сопрограммам должны предъявляться? Любые ли две функции можно сделать сопрограммами, или только удовлетворяющие какому-то общему требованию?

В общем, любые мысли приветствуются.
Re: Сопрограммы
От: sdf
Дата: 03.06.12 18:47
Оценка:
Здравствуйте, x-code, Вы писали:
XC>Интересно, как можно такую фичу грамотно спроектировать при разработке собственного языка программирования?
Посмотреть, как это уже сделано в других языках? (Lisp, Python, генераторы в C#)?
Re: Сопрограммы
От: DarkGray Россия http://blog.metatech.ru/post/ogni-razrabotki.aspx
Дата: 03.06.12 19:15
Оценка:
XC>Интересно, как можно такую фичу грамотно спроектировать при разработке собственного языка программирования?

Реализуется такая фича, как сохранение текущего контекста выполнения, при этом сохраняются локальные переменные и стек вызовов. В простом случае, когда yield не может быть внутри вложенных функции, сохраняются только локальные переменные, так, например, сделан yield в C#.

Основная сложность — сделать так, чтобы поведение сопрограмм было предсказуемым: чтобы сопрограмма при выполнении вместе с другой сопрограммой выдавала тот же результат, что и при одиночном выполнении. Для этого лучше всего подходит функциональный подход, в чистом или смешанном виде.

Простой вариант компиляции сопрограмм:
1. тело функции разбивается на куски код между yield-ами и операциями перехода
2. локальные переменные преобразуются в поля объекта
3. добавляется переменная, которая определяется какой кусок кода должен выполняться следующим

тогда следующий код (на некотором вымышленном языке)
int F(int count)
{
  var sum = 0;
  for (var i = 0; i < count; ++i)
  {
     sum += i;
     yield;
  }
  return sum;
}

при компиляции преобразуется в:
class F_Context
{
  public F_Context(int count) {this.count = count;}
  public int sum = 0;
  public int count = 0;
  public int i = 0;
  public int result;
  public int state = 0;
}
void F(F_Context context)
{
  switch (context.state)
  {
     case 0:
      context.sum = 0;
      context.i = 0;
      context.state = 1;
      goto 1;
     case 1:
      if (context.i < context.count)
        goto 3;
      context.sum += context.i;
      context.state = 2;
      return;
     case 2:
       context.i = context.i + 1;
       context.state = 1;
       goto 1;
     case 3:
      context.result = context.sum;
      context.state = -1;
      return;     
  }
}
Re: Сопрограммы
От: Mazay Россия  
Дата: 03.06.12 19:18
Оценка:
Здравствуйте, x-code, Вы писали:

XC>На википедии есть некий пример сопрограмм.

...

XC>Интересно, как можно такую фичу грамотно спроектировать при разработке собственного языка программирования?

С точки зрения языка тут особо нечего проектировать. Мне бы больше всего понравились обычные функции-интринсики.
А! Вспомнил! Хорошо бы было в типе функции как-то специфицировать, что она может прерываться/восстанавливаться. Это и для читабельности кода полезно, и компилятору может поможет.

Здесь ИМХО больше сложностей будет с точки зрения разработки компилятора. Надо будет чётко определить, что нужно сохранять при переключении между функциями и как это аккуратно восстановить потом (и как при этом не помешать оптимизации кода).
Помедитируй над ucontext.h и longjmp()/setjmp() — народ их пользует, но с опаской.

XC>Какие требования, ограничения к сопрограммам должны предъявляться? Любые ли две функции можно сделать сопрограммами, или только удовлетворяющие какому-то общему требованию?

Точно не скажу, но стоит обратить внимание на рекурентность, размер стэка (его же сохранять надо будет), всякие вызовы alloca().

XC>В общем, любые мысли приветствуются.

Сначала подумайте во что это компилироваться будет, как дебаггер будет работать, как в крэш-дампах вызовы корутин отображать.
Главное гармония ...
Re: Сопрограммы
От: Sinclair Россия https://github.com/evilguest/
Дата: 04.06.12 06:34
Оценка:
Здравствуйте, x-code, Вы писали:

XC>На википедии есть некий пример сопрограмм. Если привести его в более сиподобный вид, то получится примерно следующее:


XC>В общем, любые мысли приветствуются.

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

Было бы здорово иметь ещё и обратную операцию — т.е. когда энергичный потребляющий код автоматически переписывается в ленивый.
То есть из вот такого:
public int Sum(IEnumerable<int> input)
{
  int sum = 0;
  foreach(item in input) 
    sum += item;
  return sum;
}

мы делаем что-то вроде
public class Sum
{
  int _sum;
  public Sum() { _sum=0;}
  public void Input(int item, bool end)
  {
    _sum += item;
  }
  public int Result { return _sum;}
}
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Сопрограммы
От: Abyx Россия  
Дата: 04.06.12 06:43
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Не очень понятно, какие конкретно проблемы предлагается решить при помощи сопрограмм.

асинхронный код.
например, async/await в .NET
In Zen We Trust
Re: Сопрограммы
От: c-smile Канада http://terrainformatica.com
Дата: 04.06.12 07:04
Оценка: 3 (1)
Здравствуйте, x-code, Вы писали:

XC>Интересно, как можно такую фичу грамотно спроектировать при разработке собственного языка программирования?

XC>Какие требования, ограничения к сопрограммам должны предъявляться? Любые ли две функции можно сделать сопрограммами, или только удовлетворяющие какому-то общему требованию?

yield реализуется сохранением стека в переменной ассоциированной с function instance.

XC>В общем, любые мысли приветствуются.


А вообще если в языке поддерживаются inner functions то coroutines делаются/эмулируются достаточно легко и так.
Скажем вот генератор который выдает элементы массива на JavaScript:

function nothing() {}

function generator(arr)
{
  var i = -1;
  return function() { return (++i < arr.length)? arr[i]:nothing; } 
}

// используем

var arr = [0,1,2,3,4,5];
var gen = generator(arr);
while((el = gen()) !== nothing) {
  ...
}


Очевидно что generator() ведет себя в точности как coroutine сохраняя состояние исполнения между вызовами.
Re[2]: Сопрограммы
От: hardcase Пират http://nemerle.org
Дата: 04.06.12 08:51
Оценка:
Здравствуйте, Sinclair, Вы писали:

  Скрытый текст
S>То есть из вот такого:
S>
S>public int Sum(IEnumerable<int> input)
S>{
S>  int sum = 0;
S>  foreach(item in input) 
S>    sum += item;
S>  return sum;
S>}
S>

S>мы делаем что-то вроде
S>
S>public class Sum
S>{
S>  int _sum;
S>  public Sum() { _sum=0;}
S>  public void Input(int item, bool end)
S>  {
S>    _sum += item;
S>  }
S>  public int Result { return _sum;}
S>}
S>


Это уже суперкомпиляция какая-то получается, осилить ее очень трудно.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[2]: Сопрограммы
От: hardcase Пират http://nemerle.org
Дата: 04.06.12 08:54
Оценка: 2 (1)
Здравствуйте, Sinclair, Вы писали:

Кто-то осислил специализатор для CIL.
/* иЗвиНите зА неРовнЫй поЧерК */
Re: Сопрограммы
От: a_jelly Россия  
Дата: 04.06.12 13:43
Оценка: 2 (1)
Здравствуйте, x-code, Вы писали:

XC>Есть две функции, которые могут где-то сохранять свое состояние и передавать друг другу управление напрямую, без вызова функции. Оператор yield делает нечто, что сохраняет состояние первой функции, переключается на вторую и восстанавливает состояние второй. Желательно, чтобы это нечто было достаточно низкоуровневым и эффективным, с тем чтобы данный оператор можно было применять где угодно, в том числе при программировании embedded софта и т.п. То есть никаких фреймворков нет, есть как-бы "голый си" (некий разрабатываемый си-подобный язык).


XC>Интересно, как можно такую фичу грамотно спроектировать при разработке собственного языка программирования?

XC>Какие требования, ограничения к сопрограммам должны предъявляться? Любые ли две функции можно сделать сопрограммами, или только удовлетворяющие какому-то общему требованию?

XC>В общем, любые мысли приветствуются.


Лучшая низкоуровневая реализация сопрограмм (правда, только для x86) которую я видел (libconcurrent), написана каким-то японцем. В принципе, глядя на нее можно многое понять, тем более, что там нет завязок на setjump/longjump и setcontext/getcontext. Работает достаточно быстро.

Некоторые ограничения на сопрограммы довольно очевидны — например, не стоит включать в контекст указатели на переменные стека. Могут возникнуть какие-то сложности с многопоточными программами (т.е. надо не запутаться между стеком thread-а и стектом coroutine). Еще может возникнуть противоречие между механизмом исключений и сопрограммами, т.к. они используют похожие методы. В общем — проблем хватает.

Теоретический аспект Coroutine хорошо описан Роберто нашим, Иерусалимским вот в этой статье.

Как-то так.
Re[3]: Сопрограммы
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.06.12 15:30
Оценка:
Здравствуйте, hardcase, Вы писали:

H>Кто-то осислил специализатор для CIL.


А можно в двух словах о том, что эта штука делает?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Сопрограммы
От: hardcase Пират http://nemerle.org
Дата: 04.06.12 18:44
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, hardcase, Вы писали:


H>>Кто-то осислил специализатор для CIL.


VD>А можно в двух словах о том, что эта штука делает?


А я тебе полгода назад давал ссылку на пдф-ку по этому Cilpe.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[3]: Сопрограммы
От: Sinclair Россия https://github.com/evilguest/
Дата: 05.06.12 06:43
Оценка:
Здравствуйте, hardcase, Вы писали:


H>Это уже суперкомпиляция какая-то получается, осилить ее очень трудно.

Никакого отношения к суперкомпиляции это не имеет. Трансформация в целом аналогична трансформациям для yield return.
Почитайте про Rx и дуализм енумераторов и событий.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: Сопрограммы
От: hardcase Пират http://nemerle.org
Дата: 05.06.12 09:23
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Здравствуйте, hardcase, Вы писали:



H>>Это уже суперкомпиляция какая-то получается, осилить ее очень трудно.

S>Никакого отношения к суперкомпиляции это не имеет. Трансформация в целом аналогична трансформациям для yield return.
S>Почитайте про Rx и дуализм енумераторов и событий.

Все верно Я невнимательно посмотрел на код. Действительно, операция "выворачивания на изнанку" кода примера в целом аналогична переписыванию в yield-автомат. Сделать аналог в Nemerle в принципе реально.
/* иЗвиНите зА неРовнЫй поЧерК */
Re: Сопрограммы
От: e-Xecutor Россия  
Дата: 09.06.12 11:03
Оценка:
Здравствуйте, x-code, Вы писали:

Я недавно реализовал coroutines в своём DSL.
В общем-то всё просто. Переключение контекстов.
Coroutines организуют дерево.
Главное ограничение — один объект coroutine может быть в дереве один раз.
Это не значит, что одна функция может быть один раз. Просто для каждого вхождения нужен свой контекст.

func f()
  for i in 1..100
    yield i
  end
end

for i in f
  print(i)
end


Тут for обнаруживает, что f это функция, создаёт coroutine — отдельный контекст исполнения с собственным стэком и точкой входа f в качестве текущей инструкции, и переключает исполнение на неё.
Родителем этой coroutine будет основной контекст, в точкой возврата специальный код отслеживающий закончилась ли coroutine или вернула значение.
Ну и yield переключает контекст в родительский, и возвращает значение.

Можно делать так:
func g()
  for i in 1..100
    yield i
  end
end

func f()
  for i in g
    yield i
  end
end

for i in f
  print(i)
end


без проблем.

Вот пример linq подобного запроса на coroutine:


class Query(src)
  src
  func Select(sfunc)
    srcVal=src
    src=func()
      for val in srcVal
        yield sfunc(val)
      end
    end
    return self
  end
  func Where(wfunc)
    srcVal=src
    src=func()
      for val in srcVal
        if wfunc(val)
          yield val
        end
      end
    end
    return self
  end
  func SelectMany(csfunc,sfunc)
    srcVal=src
    src=func()
      for val in srcVal
        for val2 in csfunc(val)
          yield sfunc(val,val2)
        end
      end
    end
    return self
  end
  func get()
    return src
  end
end

class PetOwner(name,pets)
  name
  pets
end

  a=[
    PetOwner("Higa",["Scruffy", "Sam"])
    PetOwner("Ashkenazi",["Walker", "Sugar"])
    PetOwner("Price",["Scratches", "Diesel"])
    PetOwner("Hines",["Dusty"])
  ]

q=Query(a).
  SelectMany(!\1.pets,!{owner=>\1,pet=>\2}).
  Where(!\1->pet[0]=="S").
  Select(!{oname=>\1->owner.name,pname=>\1->pet}).get()

for i in q
  print("$i->oname:$i->pname")
end


здесь evaluation будет происходить лениво, каждый этап в своей coroutine.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.