Re[2]: Ленивость vs. вычисление когда доступны данные
От: VladD2 Российская Империя www.nemerle.org
Дата: 03.07.15 15:17
Оценка:
Здравствуйте, ionoy, Вы писали:

I>Если получится сделать из этого DSL, то будет дюже круто.


Что значит получится? Уже.

Только он у нас пока что узко специализированный. Мы его используем для расчетов по AST-у. Естественно, под капотом никаких IObservable, замыканий и прочей траты памяти. На объект есть только одно лишнее битовое поле. Код вычислений выглядит как обычный:

  declarations GenericType : Type
  {
    out Symbol : GenericTypeSymbol;
  stage 1:
    out BodyScope : Scope;

    Modifiers.FlagsIn                 = Modifiers.None;
    TypeParameters.IndexIn            = 0;
    TypeParameters.PrevTypeParameters = Utils.TryGetPrevTypeParameters(this);
    Members.Parent                    = Symbol;
    BodyScope                         = Symbol.MakeBaseTypesScope(this.Scope);
    TypeBase.Scope                    = BodyScope;
    TypeParameterConstraints.Scope    = BodyScope;
    Members.TypeScope                 = BodyScope;
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Ленивость vs. вычисление когда доступны данные
От: VladD2 Российская Империя www.nemerle.org
Дата: 03.07.15 15:25
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>А зачем предсказуемый порядок выполнения?


Хотя бы для той же отладки.

EP>Оптимизаторы кода переставляют инструкции местами как им удобнее, если это не меняет семантику.

EP>Более того, например в C++ не определён порядок вычисления аргументов функций: f(foo(), bar()).

Ага. Вот только не ясно почему в его наследниках (C# и Java) порядок вычисления был четко определен в спецификации?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Ленивость vs. вычисление когда доступны данные
От: VladD2 Российская Империя www.nemerle.org
Дата: 03.07.15 16:44
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Каким образом? "идея побочных эффектов при вычислениях" — она о других аспектах. А под тот аспект который по всей видимости ты имеешь в виду, например попадает остановка при внезапной сборке мусора.


Нет. В ленивом языке вычисления откладываются даже если все данных для них есть. Так что ставить, например, печать результата выражения сразу за ним бесполезно. Пока вычисления не понадобятся кому-то другому этот код не вычислится.

В языке о котором говорю я все вычистился и на результат можно будет посмотреть в том месте где его ожидает "императивный" программист. Во всем же остальном язык больше похож на ленивый ФЯ.

EP>Ключевые слова для поиска: spreadsheet, reactive programming, future/promise, и как выше уже сказали — topological sorting.


Это все (кроме последнего) имеет мало отношения к теме. Речь же идет о ЯП с подобной семантикой, а не о фичах позволяющих ее эмалировать.

EP>Теряется главная особенность ленивости — уменьшение алгоритмической сложности, которая например позволяет задавать бесконечные списки.


Причем тут алгоритмическая сложность? Что касается бесконечных списков, то вряд ли они необходимы постоянно. И, если нужно, их не так уж сложно реализовать или предоставить в виде библиотечного решения. Тот же IEnumerable<T> обладает тем иже свойствами отгороженности вычисления и бесконечности.

EP>Или например получение нескольких алгоритмов из одного кода — линеаритмической сортировки и линейного алгоритма выбора.


Это все здорово, но мало пригодно для "гражданского" применения простыми смертными. А, вот, тот же spreadsheet (Ёкскель) постоянно используют люди даже не относящиеся к программированию. Для них нужна выразительность, безопасность и удобность, а не возможность почесать правое ухо пяткой левой ноги.

EP>А что там будет помимо выражений, и как это будет выглядеть?


Наш DSL я (вкратце) описал здесь
Автор: VladD2
Дата: 03.07.15
. А, вообще, в перспективе можно создать интересный функцонально-объектно-ориентированный язык общего назначения. Для нужд обработки данных на деревьях или графах он точно не плохо подходит.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Ленивость vs. вычисление когда доступны данные
От: dsorokin Россия  
Дата: 04.07.15 09:37
Оценка:
Здравствуйте, WolfHound, Вы писали:

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


D>>Комбинатор фиксированной точки получится сделать?

WH>Нам ехать, а не шашечки.
WH>Причём ехать быстро. Ибо задача сугубо практическая.

Рекурсивные вычисления как раз таки очень удобны для практики. Если более конкретно, то это так называемая "рекурсивная нотация do". Я использую у себя для краткого определения обыкновенных дифференциальных уравнений, например.

В основе реализации рекурсивных вычислений лежит идея о комбинаторе неподвижной точки. Это обобщение идеи. А реализуется через ленивость. Как обычно, в Haskell реализован общий механизм в отличие от упрощенных Scala и F#, где тоже есть что-то подобное.

Это все частности. Теперь более фундаментальное.

Подразумевает ли "новая" стратегия вычислений, что язык должен быть ссылочно-прозрачным? Если да, то это неминуемо ведет к тому требованию, что язык должен быть еще и чистым. Или ссылочная прозрачность по боку?
Re: Ленивость vs. вычисление когда доступны данные
От: BulatZiganshin  
Дата: 04.07.15 16:50
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>А, что если вместо ленивой модели вычислений, в которой доступность данных определяется их необходимостью, применять модель вычислений в которой вычисление определяется доступностью входных данных?


наиболее близко это к out-of-order execution в cpu

VD>Однако так как вычисления происходят сразу же как будут доступны зависимости, не возникает проблем с побочными эффектами, отладкой и т.п.


а по мне наоборот — если в ленивом яхыке порядок вычислений диктуется самими данными (вычисляя a*b, мы рекурсивно погружаемся в вычисление а и b), то в таком языке вычислив x, мы внезапно начинаем вычислять сотни переменных, зависящих от него, причём ещё и в восходящем порядке. неужели это будет кому-то понятней??

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

наконец, реализация (которую я смог придумать) требует для каждой переменной создавать динамический список зависящих от неё напрямую переменных, плюс динамически присваиваемая лямбда для вычисления и признак "уже вычислено". не похоже что это эффективней ленивости

в целом, по сравнению с ленивостью выходит менее удобно для отладки, требует больше вычислений (вычисляем всё что можно, а не всё что нужно), порядок вычислений так же недетерминирован (следует отличать детерминированность модели и конкретной реализации), и реализация похоже будет менее эффективной

но оригинально, с чем и поздравляю
Люди, я люблю вас! Будьте бдительны!!!
Re[2]: Ленивость vs. вычисление когда доступны данные
От: VladD2 Российская Империя www.nemerle.org
Дата: 06.07.15 23:33
Оценка:
Здравствуйте, BulatZiganshin, Вы писали:

BZ>наиболее близко это к out-of-order execution в cpu


Ну, это немного из другой оперы. Разве что похоже.

BZ>а по мне наоборот — если в ленивом яхыке порядок вычислений диктуется самими данными (вычисляя a*b, мы рекурсивно погружаемся в вычисление а и b), то в таком языке вычислив x, мы внезапно начинаем вычислять сотни переменных, зависящих от него, причём ещё и в восходящем порядке. неужели это будет кому-то понятней??


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

Если представить вычисления как сортированный граф зависимостей где с одной стороны узлы с константами (входными данными), а на выходе рассчитываемые значения на конечных "ветвях", то ленивое вычисление будет обратный обход графа, от требуемых значений к константным, в то время как энергичное, наоборот от константных и "вниз" по графу до самых "листовых".

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

BZ>то же самое с побочными эффектами — никаких технических проблем с ними в хаскеле нет, они запрещены по идеологическим причинам, чтобы разделоить мат. понятие вычисления и алг. понятие выполнения


Проблема в том, что если значение не "запрашивать", то расчет производиться не будет, а стало быть, не будет и побочного эффекта. В остальном, согласен.

BZ>наконец, реализация (которую я смог придумать) требует для каждой переменной создавать динамический список зависящих от неё напрямую переменных, плюс динамически присваиваемая лямбда для вычисления и признак "уже вычислено". не похоже что это эффективней ленивости


Ну, мы сделали реализацию для своих нужд которая позволяет обсчитывать, с помощью такого языка, значения на дереве (AST). Посчитано значение или нет хранится в битовом поле каждого узла АСТ и составляет по биту на зависимое свойство (там мы называем такие значения). Не эффективность заключается в том, что наш алгоритм расчета "нарезает круги" по AST, и при неблагоприятных обстоятельствах, этих кругов может оказаться больше чем нужно для линейного расчета. Мы не можем создавать граф зависимостей статически или динамически, так как наш AST динамически изменяемый. Но мы производим локальную сортировку (для одного узла) и полагаемся на то, что на практике дерево в 99% случаев обходится справа-налево, сверху-вниз (за исключения случаев, когда оно намеренно образует отдельные проходы). В итоге получается хорошая производительность. Но у нас все же не полноценный язык, а DSL ограниченных обработкой данных на дереве (AST).

Я не знаю какие механизмы используются в том же Хаскеле для эффективного расчета ленивой модели. Если накидаешь ссылок, буду благодарен.

BZ>в целом, по сравнению с ленивостью выходит менее удобно для отладки,


Для отладки, как раз очень удобно. Можно трассировать. Можно ставить точки останова в каждом присвоении и смотреть значения. А, можно выполнять по шагам переходя на следующее вычисляемое значение.

BZ>требует больше вычислений (вычисляем всё что можно, а не всё что нужно),


Вот это большой вопрос. Уверен, что ленивые вычисления не даются бесплатно. Хотелось бы оценить эти затраты и сравнить с теми что имеем мы. В идеале, можно даже попробовать сделать гибридную систему. Она бы нам не помешала. В режиме IDE ленивость была бы востребована на начальных этапах загрузки для ускорения отрисовки и приведения GUI в рабочий режим. Ну, а потом уже можно пустить все вычисления на отдельных потоках для ускорения расчетов и отклика IDE.

BZ>порядок вычислений так же недетерминирован (следует отличать детерминированность модели и конкретной реализации), и реализация похоже будет менее эффективной


Да, есть такое. Но конкретный алгоритм расчета дает повторяемый результат. В прочем, при попытке распараллелить мы опять же получим недетерминизм. Но мы планируем сделать систему с контролируемыми побочными эффектами. Побочные эффекты в ней можно будет применять только для инициализации отдельных обхектов (например, таблиц имен) и на определенных стадиях. Мы ввели в язык явные стадии. На стадии на которой инициализируется переменная в нее можно только набивать данные (имена для таблицы имен). Операция эта коммутативная и ассоциативная (в математическом смысле), так что ее можно выполнять в любом порядке. На следующей стадии объект переключается в режим использования и из него можно извлекать данные (например, производить связывание по таблице имен).

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

BZ>но оригинально, с чем и поздравляю


Спасибо. Вот только не верится, что нам эта идея пришла первым в голову. Интересно посмотреть на другие похожие. Плюс интересно сравнить его с ленивым исполнением и посмотреть нельзя ли связать эти оба механизма чтобы получить в одном языке преимущества обоих подходов. Чтобы ленивость была даже не по требованию того кто пишет программу, а по необходимости ее потребителя. Грузим мы проект в IDE — переходим в ленивый режим чтобы максимально быстро отобразить данные той функции что у программиста на экране и т.п. А, потом запускаем по потоку для каждого процессора и даем им в пакетном режиме протипировать AST всех фалов проекта.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Ленивость vs. вычисление когда доступны данные
От: Manticore США http://github.com/fjarri
Дата: 07.07.15 01:16
Оценка:
Здравствуйте, dsorokin, Вы писали:

D>Рекурсивные вычисления как раз таки очень удобны для практики. Если более конкретно, то это так называемая "рекурсивная нотация do". Я использую у себя для краткого определения обыкновенных дифференциальных уравнений, например.


А можно пример кода? Я как раз сейчас занимаюсь сходной проблемой, было бы интересно посмотреть.
Re[5]: Ленивость vs. вычисление когда доступны данные
От: dsorokin Россия  
Дата: 07.07.15 05:49
Оценка: 2 (1)
Здравствуйте, Manticore, Вы писали:

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


D>>Рекурсивные вычисления как раз таки очень удобны для практики. Если более конкретно, то это так называемая "рекурсивная нотация do". Я использую у себя для краткого определения обыкновенных дифференциальных уравнений, например.


M>А можно пример кода? Я как раз сейчас занимаюсь сходной проблемой, было бы интересно посмотреть.


Можно поспорить с тем, насколько это практично. Для меня даже очень:

model :: Simulation Results
model = 
  mdo a <- integ (- ka * a) 100
      b <- integ (ka * a - kb * b) 0
      c <- integ (kb * b) 0
      let ka = 1
          kb = 1
      return $ results
        [resultSource "a" "variable A" a,
         resultSource "b" "variable B" b,
         resultSource "c" "variable C" c]


Это моя Айвика (англ. Aivika). У нее конек в сильной поддержке дискретно-событийного моделирования, но есть и поддержка системной динамики.

В примере самое интересное в том, как определяются три интеграла a, b и c. Каждый имеет тип Dynamics Double, но функция integ возвращает аппроксимацию интеграла в рамках вычисления Simulation по заданным производной и начальному значению, а точнее

integ :: Dynamics Double -> Dynamics Double -> Simulation (Dynamics Double)


Фокус в том, что Simulation является MonadFix, а потому можно использовать рекурсивную нотацию do. Здесь в примере это обозначается ключевым словом "mdo".

Да, еще для понимания. Dynamics Double является Num:

instance Num a => Num (Dynamics a)
Re[5]: Ленивость vs. вычисление когда доступны данные
От: dsorokin Россия  
Дата: 07.07.15 06:25
Оценка:
Здравствуйте, Manticore, Вы писали:

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


D>>Рекурсивные вычисления как раз таки очень удобны для практики. Если более конкретно, то это так называемая "рекурсивная нотация do". Я использую у себя для краткого определения обыкновенных дифференциальных уравнений, например.


M>А можно пример кода? Я как раз сейчас занимаюсь сходной проблемой, было бы интересно посмотреть.


Вот, более интересный пример. Навеяно примером из документации к Vensim:

https://github.com/dsorokin/aivika-experiment-chart/blob/master/examples/Financial/Model.hs
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.