Ликбез по ComputationExpressions
От: hardcase Пират http://nemerle.org
Дата: 22.04.10 19:43
Оценка:
Недавно было обсуждение сабжа
Автор: dsorokin
Дата: 17.04.10
. Так вот, не могли бы сведущие в предмете (ComputationExpressions) объяснить таким танкистам, как я, какие задачи решает эта система макросов, для чего она нужна?
/* иЗвиНите зА неРовнЫй поЧерК */
Re: Ликбез по ComputationExpressions
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.04.10 19:55
Оценка:
Здравствуйте, hardcase, Вы писали:

H>Недавно было обсуждение сабжа
Автор: dsorokin
Дата: 17.04.10
. Так вот, не могли бы сведущие в предмете (ComputationExpressions) объяснить таким танкистам, как я, какие задачи решает эта система макросов, для чего она нужна?


Главное ее предназначение — доказательство того, что язык с макросами на которых можно реализовать некоторую фичу мощнее чем язык в который встроили такую фичу.

Ну, а если серьезно, то это реализация одной фичи из F# и (от части) Haskell-я:
http://en.wikibooks.org/wiki/F_Sharp_Programming/Computation_Expressions
http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/manual/spec.html#_Toc257733552
http://msdn.microsoft.com/en-us/library/dd233182.aspx
http://langexplr.blogspot.com/2008/10/using-f-computation-expressions-to-read.html
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Ликбез по ComputationExpressions
От: hardcase Пират http://nemerle.org
Дата: 22.04.10 20:00
Оценка:
Здравствуйте, VladD2, Вы писали:

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


H>>Недавно было обсуждение сабжа
Автор: dsorokin
Дата: 17.04.10
. Так вот, не могли бы сведущие в предмете (ComputationExpressions) объяснить таким танкистам, как я, какие задачи решает эта система макросов, для чего она нужна?


VD>Главное ее предназначение — доказательство того, что язык с макросами на которых можно реализовать некоторую фичу мощнее чем язык в который встроили такую фичу.


Мило.

VD>Ну, а если серьезно, то это реализация одной фичи из F# и (от части) Haskell-я:

VD>http://en.wikibooks.org/wiki/F_Sharp_Programming/Computation_Expressions
VD>http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/manual/spec.html#_Toc257733552
VD>http://msdn.microsoft.com/en-us/library/dd233182.aspx
VD>http://langexplr.blogspot.com/2008/10/using-f-computation-expressions-to-read.html

Интересно, а можно ли ко всей этой кухне прикрутить сохранение (сериализацию) и возобновление состояния?
/* иЗвиНите зА неРовнЫй поЧерК */
Re[3]: Ликбез по ComputationExpressions
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.04.10 20:25
Оценка:
Здравствуйте, hardcase, Вы писали:

H>Интересно, а можно ли ко всей этой кухне прикрутить сохранение (сериализацию) и возобновление состояния?


Вряд ли. Там все завязано на монадах которые выполняют одну роль — связывают несколько вычислений происходящих в ленивой манере.

Это позволяет получить нечто похожее на континюэшон, но серализовать состояние не удастся, так как все основано на связанных ссылках на функции.

В прочем, я тут совершенный профан. Пусть лучше скажут те кто в этом что-то понимает.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Ликбез по ComputationExpressions
От: dsorokin Россия  
Дата: 23.04.10 04:15
Оценка:
Здравствуйте, hardcase, Вы писали:

H>Интересно, а можно ли ко всей этой кухне прикрутить сохранение (сериализацию) и возобновление состояния?


Не совсем понятно, что ты имеешь ввиду. А вообще, продолжение является монадой. Поэтому если ты можешь решить свою задачу через продолжение, то есть шансы, что можно создать соответствующее вычислительное выражение. Кстати, async из F# построен на основе продолжения, и он умеет передавать свое состояние между потоками, так же как и распространять исключения между ними.
Re: Ликбез по ComputationExpressions
От: dsorokin Россия  
Дата: 23.04.10 04:18
Оценка:
Здравствуйте, hardcase, Вы писали:

H>какие задачи решает эта система макросов, для чего она нужна?


Я бы их использовал уже за то, что она умеет enumerable comprehension. Когда легко и непринужденно можно создавать объекты типа IEnumerable[_] в любом месте программы. Генераторы последовательности. В F# это зовется sequence comprehension. Ключевое слово seq. Здесь формат другой: comp enumerable {...}.
Re: Ликбез по ComputationExpressions
От: dsorokin Россия  
Дата: 23.04.10 07:08
Оценка: 111 (3)
Здравствуйте, hardcase, Вы писали:

H>какие задачи решает эта система макросов, для чего она нужна?


Последние новости. Сейчас работаю над хвостовой оптимизацией для enumerable comprehension (пока на залили в svn). Возможны финты типа:

    def stream (n)
    {
      comp enumerable
      {
        yield n;
        yieldcomp (stream (n + 1));
      }
    }


Кстати, работает быстрее, чем аналогичная вещь на F# Вообще, еnumerable comprehension создает ленивую последовательность, которая вычисляется только по требованию. Иногда незаменимая вещь.

Вот, более полезный пример. Лениво возвращает список всех файлов, включая вложенные, из заданного каталога.

    
    def filesUnderFolder (rootFolder : string) : IEnumerable [string]
    {
      comp enumerable
      {
        foreach (file in System.IO.Directory.GetFiles (rootFolder))
          yield file;
        foreach (dir in System.IO.Directory.GetDirectories (rootFolder))
          yieldcomp (filesUnderFolder (dir))
      }
    }
Re[2]: Ликбез по ComputationExpressions
От: Anton V. Kolotaev  
Дата: 23.04.10 09:54
Оценка:
Здравствуйте, dsorokin, Вы писали:

D>Вот, более полезный пример. Лениво возвращает список всех файлов, включая вложенные, из заданного каталога.


D>
    
D>    def filesUnderFolder (rootFolder : string) : IEnumerable [string]
D>    {
D>      comp enumerable
D>      {
D>        foreach (file in System.IO.Directory.GetFiles (rootFolder))
D>          yield file;
D>        foreach (dir in System.IO.Directory.GetDirectories (rootFolder))
D>          yieldcomp (filesUnderFolder (dir))
D>      }
D>    }
D>


Для сравнения оригинал на F#:
   let rec getAllFiles pattern folder  = 
      seq { for x in Directory.GetDirectories folder do yield! getAllFiles pattern x 
            for x in Directory.GetFiles(folder, pattern) do yield x }
Re[2]: Ликбез по ComputationExpressions
От: WolfHound  
Дата: 23.04.10 10:01
Оценка: 39 (1) +1
Здравствуйте, dsorokin, Вы писали:

Несколько замечаний по коду:

  1. Начинай сообщение для коммита со строки:

    [Snippets] Computation Expressions

    Чтобы было проще понимать что да коммит.


  2. Не экономь на файлах.
    Лучше много маленьких чем мало больших.
    Ибо так гораздо удобнее искать что где лежит.


  3. EmptyEnumerable[T]/EmptyEnumerator[T] не имеют состояния. Следовательно их можно раз и на всегда закешировать в статической переменной.


  4. Зачем так сложно?
      public module EnumerableHelper
      {
    ...    
        public Delay[T] (cont : void -> IEnumerable[T]) : IEnumerable[T]
        {
          foreach (t in cont ())  yield t
        }


    Почему не:
        public Delay[T] (cont : void -> IEnumerable[T]) : IEnumerable[T]
        {
          cont ();
        }



  5. Тут ты тоже перемудрил. Так лучше:
      public variant LazyStream[T]
      {
        | Nil;
        | Cons { value : T; cont : void -> LazyStream[T] }
        
        public ToEnumerable () : IEnumerable[T]
        {
          def loop(_)
          {
            | Nil => ()
            | Cons (value, cont) =>
              yield value;
              loop(cont());
          }
          loop(this);
        }
      }



  6. Вместо:
          def temp = Macros.NewSymbol ();
          def loop = Macros.NewSymbol ();

    лучше писать
          def temp = Macros.NewSymbol ("temp");
          def loop = Macros.NewSymbol ("loop");

    Так макрос после рускрутки получится более читаемый.


  7. Тут происходит нечто страшное:
          def coll =
            <[
                EnumerableHelper.Delay (() =>
                  {
                    $init;
                    $postInit;
                    
                    def $(loop : name) ()
                    {
                      match ($cond)
                      {
                        | false => LazyStream.Nil ();
                        | true =>  LazyStream.Cons ($var, () => { $change; $(loop : name) () })
                      }
                    }
                    
                    $(loop : name) ().ToEnumerable ()
                  })
            ]>;

    def $(loop : name) () лишнее.
    Достаточно def loop ()
    Ибо макросы по умолчанию гигееничны.

    Но главное тут плодится куча мусора на ровном месте.
    Вот так гораздо лучше:
      public module ForHelper
      {
        public Enumerate[T](cond : void -> bool, value : void -> T, change : void -> void) : IEnumerable[T]
        {
          while (cond())
          {
            yield value();
            change();
          }
        }
      }

          def coll =
            <[
                EnumerableHelper.Delay (() =>
                  {
                    $init;
                    $postInit;
                    
                    ForHelper.Enumerate(() => $cond, () => $var, () => $change)
                  })
            ]>;



  8. Тут ИМХО лучше так:
      internal class ListBuilder : ComputationBuilder
    ...
        public override Yield (expr : PExpr) : PExpr
        {
          <[ $(acc : name).Add($expr) ]>
        }
    ...
        public override Run (expr : PExpr) : PExpr
        {
          <[
            { 
              mutable $(acc : name) = List();
              $expr;
              $(acc : name).NToList ()
            }
          ]>
        }


  9. Зачем нужно два void'а?
      public sealed class ComputationVoid
      {
        public static Value : ComputationVoid = ComputationVoid ()
      }
    
      public variant FakeVoid
      {
        | FakeVoid
      }



  10. Можно писать так:
    _testDo(10).Iter(WriteLine);

    местный вывод типов может даже в таких ситуациях разбираться...


  11. А еще я вернул ComputationExpressions.sln который ты зачемто удалил.
    Ибо многим работать в IDE банально удобнее.
... << RSDN@Home 1.2.0 alpha 4 rev. 1305>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[3]: Ликбез по ComputationExpressions
От: WolfHound  
Дата: 23.04.10 10:13
Оценка:
Здравствуйте, Anton V. Kolotaev, Вы писали:

AVK>Для сравнения оригинал на F#:

Для сравнения как следует отформатируем:
    def filesUnderFolder(pattern, rootFolder)
    {
      comp enumerable
      {
        foreach (file in Directory.GetFiles(rootFolder, pattern))
          yield file;
        foreach (dir in Directory.GetDirectories(rootFolder))
          yieldcomp filesUnderFolder(pattern, dir);
      }
    }

    let rec getAllFiles pattern folder  = 
        seq
        {
            for x in Directory.GetDirectories folder do
                yield! getAllFiles pattern x 
            for x in Directory.GetFiles(folder, pattern) do
                yield x 
        }

Итого: Разница в двух фигурных скобках и коротком алиасе который без труда можно добавить и в немерловый вариант.
  public macro @compEnumerable (expr)
  syntax ("comp", "enumerable", expr)
  {
    ComputationExpander.Expand (EnumerableBuilder.Instance, expr)
  }
  
  public macro @compEnumerableSeq (expr)
  syntax ("seq", expr)
  {
    ComputationExpander.Expand (EnumerableBuilder.Instance, expr)
  }
... << RSDN@Home 1.2.0 alpha 4 rev. 1305>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[3]: Ликбез по ComputationExpressions
От: dsorokin Россия  
Дата: 23.04.10 10:31
Оценка:
Здравствуйте, WolfHound, Вы писали:

Спасибо за замечания. Учту. List comprehension оптимизирую через массив.

WH>Почему не:

WH>
WH>    public Delay[T] (cont : void -> IEnumerable[T]) : IEnumerable[T]
WH>    {
WH>      cont ();
WH>    }
WH>


Так делать нельзя. Теряется смысл в Delay. Потом, я написал отдельный DelayedEnumerable[T] : IEnumerable[T]. Через него будет эффективнее.

WH>Вот так гораздо лучше:

WH>
WH>  public module ForHelper
WH>  {
WH>    public Enumerate[T](cond : void -> bool, value : void -> T, change : void -> void) : IEnumerable[T]
WH>    {
WH>      while (cond())
WH>      {
WH>        yield value();
WH>        change();
WH>      }
WH>    }
WH>  }
WH>

WH>
WH>      def coll =
WH>        <[
WH>            EnumerableHelper.Delay (() =>
WH>              {
WH>                $init;
WH>                $postInit;
                
WH>                ForHelper.Enumerate(() => $cond, () => $var, () => $change)
WH>              })
WH>        ]>;
WH>


На мой взгляд разницы большой нет. Но соглашусь, что так проще понять.

WH>Зачем нужно два void'а?

WH>
WH>  public sealed class ComputationVoid
WH>  {
WH>    public static Value : ComputationVoid = ComputationVoid ()
WH>  }

WH>  public variant FakeVoid
WH>  {
WH>    | FakeVoid
WH>  }
WH>


Это вопрос остается открытым. Собирался оставить один

WH>А еще я вернул ComputationExpressions.sln который ты зачемто удалил.

WH>Ибо многим работать в IDE банально удобнее.
WH>[/list]

Просто у меня нет полноценной студии. Есть VS C# Express 2005 и VS 2008 Shell. Не знаю, можно ли к ним прикрутить интеграцию?
Re[4]: Ликбез по ComputationExpressions
От: WolfHound  
Дата: 23.04.10 10:54
Оценка: 1 (1)
Здравствуйте, dsorokin, Вы писали:

D>Так делать нельзя. Теряется смысл в Delay. Потом, я написал отдельный DelayedEnumerable[T] : IEnumerable[T]. Через него будет эффективнее.

А... кажется понял в чем прикол.

D>На мой взгляд разницы большой нет. Но соглашусь, что так проще понять.

Так не только проще понять но еще и эффективнее.

D>Просто у меня нет полноценной студии. Есть VS C# Express 2005 и VS 2008 Shell. Не знаю, можно ли к ним прикрутить интеграцию?

Есть NemerleStudio. http://www.rsdn.ru/forum/prj.nemerle/2878763.1.aspx
Автор: Блудов Павел
Дата: 18.03.08

Инсталятор немерла брать ессно свежий.
Есдинственная проблема это то что NemerleStudio хочет расширение nsln вместо sln. Просто переименуй файл.
... << RSDN@Home 1.2.0 alpha 4 rev. 1305>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[4]: Ликбез по ComputationExpressions
От: hardcase Пират http://nemerle.org
Дата: 23.04.10 10:55
Оценка:
Здравствуйте, dsorokin, Вы писали:

D>Просто у меня нет полноценной студии. Есть VS C# Express 2005 и VS 2008 Shell. Не знаю, можно ли к ним прикрутить интеграцию?


К Shell-у можно. Правда я всегда путаю Isolated и Integrated. Еще можно к SharpDevelop (формат проектов теперь идентичен).
/* иЗвиНите зА неРовнЫй поЧерК */
Re[5]: Ликбез по ComputationExpressions
От: dsorokin Россия  
Дата: 23.04.10 12:06
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>Есть NemerleStudio. http://www.rsdn.ru/forum/prj.nemerle/2878763.1.aspx
Автор: Блудов Павел
Дата: 18.03.08

WH>Инсталятор немерла брать ессно свежий.
WH>Есдинственная проблема это то что NemerleStudio хочет расширение nsln вместо sln. Просто переименуй файл.

Спасибо. Похоже требуется VS 2008 Shell Isolated, а меня как раз стоит Integrated. Однако, F# там успешно работает. Придется снова качать.
Re[5]: Ликбез по ComputationExpressions
От: dsorokin Россия  
Дата: 23.04.10 12:08
Оценка:
Здравствуйте, hardcase, Вы писали:

H>К Shell-у можно. Правда я всегда путаю Isolated и Integrated. Еще можно к SharpDevelop (формат проектов теперь идентичен).


Пожалуй, SharpDevelop стоит снова мне поглядеть. Давно его не видел.
Re[4]: Ликбез по ComputationExpressions
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.04.10 14:19
Оценка:
Здравствуйте, dsorokin, Вы писали:

D>Не совсем понятно, что ты имеешь ввиду. А вообще, продолжение является монадой.


Нет не является. Продолжения вещь несомненно более мощьная. Их состояние можено сериализовать или клонировать.

Об это собственно и речь.

Представь себе, что некий ворфлоу заморозили в некоторой точке и это замороженное состояние превратили в поток байтов и записали в файл или поместили в поле БД.

Далее, через несколько дней, достали от туда, восстановили состояние в другой экземпляр и запустили с той же точки.

D>Поэтому если ты можешь решить свою задачу через продолжение, то есть шансы, что можно создать соответствующее вычислительное выражение. Кстати, async из F# построен на основе продолжения, и он умеет передавать свое состояние между потоками, так же как и распространять исключения между ними.


Как я понимаю там есть только CSP который, лично я, не понимаю как можно сериализовать или клонировать.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Ликбез по ComputationExpressions
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.04.10 14:21
Оценка:
Здравствуйте, dsorokin, Вы писали:

D>Я бы их использовал уже за то, что она умеет enumerable comprehension. Когда легко и непринужденно можно создавать объекты типа IEnumerable[_] в любом месте программы. Генераторы последовательности. В F# это зовется sequence comprehension. Ключевое слово seq. Здесь формат другой: comp enumerable {...}.


Это конечно полезно, для решения тех же задач есть и другие средства (макра лист компрешхншон, yield-методы, Linq, стандартные ФВП). Так что это скорее вопрос предпочтений и привычек.

Но, на мой взгляд, несомненно одно — чем больше возможностей тем лучше.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Ликбез по ComputationExpressions
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.04.10 14:26
Оценка:
Здравствуйте, dsorokin, Вы писали:

D>
    
D>    def filesUnderFolder (rootFolder : string) : IEnumerable [string]
D>    {
D>      comp enumerable
D>      {
D>        foreach (file in System.IO.Directory.GetFiles (rootFolder))
D>          yield file;
D>        foreach (dir in System.IO.Directory.GetDirectories (rootFolder))
D>          yieldcomp (filesUnderFolder (dir))
D>      }
D>    }
D>


Такой вопрос...

Народ пользуясь Немерлом частенько был разочарован тем, что yield нельзя применять внутри локальных функций.

Нельзя ли реализовать что-то вроде такого варианта:
    compdef filesUnderFolder (rootFolder : string) : IEnumerable [string]
    {
      foreach (file in System.IO.Directory.GetFiles (rootFolder))
        yield file;

      foreach (dir in System.IO.Directory.GetDirectories (rootFolder))
        yieldcomp (filesUnderFolder (dir))
    }

?

Вот это было бы просто супер удобно.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Ликбез по ComputationExpressions
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.04.10 14:38
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>Для сравнения как следует отформатируем:...


Да по-моему как не форматируй, а код идентичен. Количество строк и пробелов ни на что не влияет.
Точнее так чем более компактно записан один и тот же смысловой фрагмент тем сложнее его понять.

Лично для меня код отличается только при условии, что есть значительный синтаксический шум или если он отличается сематнически.

А эти два примера — это близницы браться с поправкой на синтаксис.

ЗЫ

Если уж кому-то охота заняться пенесометрией объемов кода, то наверно стоит применять identation-синтаксис.

Специально для таких любителей пенесометрии в снипетах немерла есть пример raytracer.
Вот список файлов из этого каталога.
[url=http://nemerle.googlecode.com/svn/nemerle/trunk/snippets/raytracer/ray-cs.cs]ray-cs.cs[/url]        4 249
[url=http://nemerle.googlecode.com/svn/nemerle/trunk/snippets/raytracer/ray2.ml]ray2.ml[/url]          2 793
[url=http://nemerle.googlecode.com/svn/nemerle/trunk/snippets/raytracer/ray3.ml]ray3.ml[/url]          3 030
[url=http://nemerle.googlecode.com/svn/nemerle/trunk/snippets/raytracer/ray.n]ray.n[/url]            2 962
[url=http://nemerle.googlecode.com/svn/nemerle/trunk/snippets/raytracer/ray2.n]ray2.n[/url]           3 024
[url=http://nemerle.googlecode.com/svn/nemerle/trunk/snippets/raytracer/ray3.n]ray3.n[/url]           3 470
[url=http://nemerle.googlecode.com/svn/nemerle/trunk/snippets/raytracer/ray-compressed.n]ray-compressed.n[/url] 2 725
[url=http://nemerle.googlecode.com/svn/nemerle/trunk/snippets/raytracer/ray-hand-opt.n]ray-hand-opt.n[/url]   3 574

Думаю их имена и размеры говорят сами за себя.

Языки группы ML конечно лидеры с точки зрения компактности записи кода, и Немерл от них не сильно отстает. А при должных стараниях может и перегнать.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Ликбез по ComputationExpressions
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.04.10 14:41
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>Для сравнения как следует отформатируем:

WH>
WH>    def filesUnderFolder(pattern, rootFolder)
WH>    {
WH>      comp enumerable
WH>      {
WH>        foreach (file in Directory.GetFiles(rootFolder, pattern))
WH>          yield file;
WH>        foreach (dir in Directory.GetDirectories(rootFolder))
WH>          yieldcomp filesUnderFolder(pattern, dir);
WH>      }
WH>    }
WH>


Кстати, в Немерле не указывать Directory (открыв модуль с помощью юсинга). Алгоритм вывода типов немерла это спокойно разрулит. А F# так может:
using System.IO.Directory;

def filesUnderFolder(pattern, rootFolder)
{
  comp enumerable
  {
    foreach (file in GetFiles(rootFolder, pattern))
      yield file;
    foreach (dir in GetDirectories(rootFolder))
      yieldcomp filesUnderFolder(pattern, dir);
  }
}

Мне кажется — нет.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.