Russian Lambda Planet

04 февраля 2012

Codedot

LISP-style Interaction System

Previously, we found that a natural set of agent types for uniform memory consists of one binary agent (ξ), two unary ones (φ and ψ), and another one without any auxiliary ports (ν). As planned, let us try to construct a formal interaction system based exclusively on these agents.

In order to prove universality of an interaction system, it is sufficient to provide δ, γ, and ε in its terms. So far, we are not ready to simulate the whole system of interaction combinators. However, it appears that the following LISP-style interaction rules for {ξ, φ, ψ, ν} can at least result in simulation of the two rules γ >< γ and δ >< δ as well as ε >< α, α ∈ {γ, δ, ε}:

ξ[ξ(a, b), ξ(c, d)] >< ξ[ξ(a, c), ξ(b, d)];
φ[a] >< ξ[a, ν], ψ[a] >< ξ[ν, a];
ν >< ξ[ν, ν], ν >< φ[ν], ν >< ψ[ν].

Let us notice that behavior of ξ, φ, ψ, and ν agents is similar to that of cons, car, cdr, and nil in LISP, respectively. In lambda calculus, they can be represented as P2, T, F, and λx.T. This is why we can name the agent types C («constructor»), L («left»), R («right»), and N («null») to ease notation. This way, the above rules result in the following reduction sequence which simulates δ annihilation (swapping L and R will in turn result in simulation of γ annihilation):

04 февраля 2012, 07:58

ru_lambda

Haskell+web

Товарищи писатели вебни на хаскеле! Здесь же есть такие?

Мне нужно (для личных нужд) написать веб-приложение на хаскеле, работающее с базой. Будут храниться блобы  (0.2-5 Мб) с описанием в базе (да, именно в базе, а не в файловой системе) и раздаваться авторизованным пользователям. Плюс интерфейс загрузки - отдачи, плюс контроль прав.

Существо я исключительно ленивое, поэтому чем проще в использовании будет решение, тем лучше. Какие решения вы посоветуете? А то пока я подумываю о написании CGI приложения в лоб, т.е. без сторонних библиотек, но это как-то не комильфо.

написал permea_kra 04 февраля 2012, 07:32

lionet

The C++0x “Concepts” Effort

Свежачок (29 Dec 2011). Совершенно сногсшибательная статья про историю Концептов в C++, и почему её в итоге не включили в стандарт C++0x.

http://arxiv.org/pdf/1201.0027.pdf

В тексте непременно замешан Хаскель, Окамл, моноиды, полугруппы, Страуструп, Степанов и Олег™®©.



Haskell faired particularly well in this study, with Standard ML not too far behind, while Eiffel, Java, and C# did not do as well. The underlying pattern was that the three object-oriented languages relied on F-bounded polymorphism, whereas Standard ML and Haskell did not. Standard ML supports generic programming through Functors and signatures and Haskell supports generic programming via type classes. The take-away point for us was that a design for concepts in C++ should be based on the best features of Haskell and Standard ML, and not F-bounded polymorphism. Our case study influenced other programming language researchers. For example, Chakravarty et al. added associated types to Haskell (Шрифт мой —[info]lionet), filling in the only half-circle for Haskell in Table 1.
<...>
In addition to informing the design for concepts, FG inspired the implicits feature of the Scala language. (Шрифт мой —[info]lionet) The inspiration for FG came primarily from Haskell and Standard ML.
<...>
Nevertheless, type classes provide excellent support for generic programming, combining a modular type system with the convenience of implicit instantiation of generics […]. If only the divide between the imperative and functional programming communities had not been so large!


Как всегда, самая мякотка — на странице 42.

Enjoy!

04 февраля 2012, 02:23

03 февраля 2012

Russian-speaking Scala Enthusiasts

Новости из мира Scala за 2 недели (3 Февраля 2012)

03-02-2012

Новое

Scalaxb 0.6.8! scalaxb - инструмент для связывания данных и XML на основе W3C XML схемы (xsd) или WSDL.

Specs2 Spring version 0.4. Улучшенная поддержка spring 2.5, добавлена поддержка hibernate 4, теперь можно использовать аннотацию @Bean из кода на Scala. Подробности тут.

Styla – достаточно полный и быстрый интерпретатор Пролога на Scala.

Less-sbt 0.1.5. Sbt-less позволяет компилировать less-css файлы из sbt.

Новое в блогах

Как публиковать SBT проекты в Nexus от Jan Machacek (@honzam399).

Mike Miller написал ревью на книгу “Programming Concurrency on the JVM”.

Как использовать удаленных актеров Akka 2.0 из Java, Запускаем AkkA на Android.

Интересное предложение (SIP) об инлайнинге классов в Scala.

27-01-2012

Новое

Вышел Scalaz 6.0.4! Последний релиз исправляет несколько багов, включая критический баг в scalaz.concurrent.Actor, несколько новых возможностей.

sbt-idea 1.0.0. Этот плагин для SBT автоматизирует настройку проекта для IntelliJ IDEA на основе определения SBT проекта.

shapeless 1.1.0. Проект Shapeless - исследование возможностей обобщенного программирования (generic programming) в Scala на основе типов классов (type classes) и зависимых типов.

Доступен scalatra 2.0.3. Scalatra - легковесный веб-фрейворк для Scala, вдохновленный Sinatra для Ruby.

Akka-1.3-RC7. Небольшие улучшения и исправления багов.

Akka 2.0 pre-release Milestone 3. Финальный релиз все ближе и ближе! Эта версия включает исправления, улучшенную документацию и несколько новых возможностей.

loglady 1.0.0. loglady - безумно простой API для логирования для Scala, обертка slf4j.

sbt-assembly 0.7.3. sbt-assembly - плагин для SBT для создания из проекта одного jar-файла, который включает в себя все зависимости.

Обновился план разработки Akka 2.x.

groll, плагин для SBT для просмотра и навигации по измениям в истории Git обновился до версии 1.2.0

bytecask 0.1.1. bytecask - база данных “ключ-значение”, вдохновленная Bitcask.

Lift Shiro 0.0.5, интеграция между Apache Shiro и Lift.

sbt-native-packager 0.2.0, плагин для SBT создания инсталляционных пакетов, включая Windows, Debian, RedHat.

Новое в блогах

Скоро в Scala – Futures and Promises.

Нет, я не должен вам scala-tools.org от David Pollak (@dpp).

DocBook Plugin для SBT by Ndidi Alaneme (@AmarettoAndCode).

Микфиксные операторы и комбинаторы парсеров, Бонус от Erkki Lindpere (@t4ffer).

JavaScript и Scala: хорошее и плохое от Graham Lea.

“Баг” в Scala и внедрение зависимостей (CDI Dependency Injection) by Hendy Irawan (@hendyirawan).

Отличное введение в Scala-макросы (PDF!) от Евгения Бурмако (@xeno_by).

Байки о реконструкции. Часть 1: Потерянная нить by Nathan Hamblen (@n8han).

Liftweb Bootstrap – хороший базовый (стартовый проект) от by Franz Bettag (@fbettag). Проект на github. Хорошая начальная точка для того, чтобы быстро начать свой проект на Lift и Twitter Bootstrap.

Про интерполяцию строк в Scala 2.10 от Daniel Sobral (@dcsobral).

Q&A: Введение в Scala с Одерски (@odersky).

Алгебры списков и комбинатор неподвижной точки Mu от Debasish Ghosh (@debasishg).

Руководство по миграции с AkkA 1.3.x на 2.0.x.

Экосистема Java и Scala ABI версии от Havoc Pennington (@havocp).(ABI = Application Binary Interface).

Sonatype выложил инструкции как публиковать артефакты на oss.sonatype.org из SBT.

Marc-Daniel Ortega (@patterngazer) написал об использовании фреймовка Disruptor на Scala в посте A Start Trek firing Disruptors from Scala.

Вторая часть интервью с Viktor Klang.

написал scala-enthusiasts-belarus@googlegroups.com (Scala Enthusiasts Belarus) 03 февраля 2012, 08:00

02 февраля 2012

Dee Mon journal

ATS madness: smoothsort с доказательством

Церебральный секс в 7 актах.

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

Введение


Для тренировки своего ATS-фу я решил реализовать и доказать на нем алгоритм smoothsort - сортировку, имеющую сложность O(n * log n) как в среднем, так и в худшем случае, O(n) в лучшем, и вроде как плавно изменяющую скорость по мере перехода от лучшего случая к среднему (по мере увеличения числа элементов не на своих местах), отсюда ее название. Моей задачей было получить корректно работающую реализацию, содержащую в явном виде доказательство отсортированности получаемого массива. Заставить ее работать супер быстро я задачу не ставил, т.к. по-любому константа в сложности этого алгоритма так велика, что на практике он проигрывает по скорости более простым методам на малых объемах и гибридным на больших. Поэтому я позволил себе неэффективную реализацию с использованием O(log n) памяти под вспомогательные структуры. В интернетах пишут, что Аллах послал нам эту сортировку через пророка своего Дейкстру в варианте с О(1) дополнительной памяти, но это все равно враки, потом расскажу подробней почему.

Акт 1. Алгоритм


Алгоритм строится вокруг упоминавшихся тут ранее чисел Леонардо. Напомню, что определяются они так:
L[0] = 1, L[1] = 1, L[k] = L[k-1] + L[k-2] + 1
Вводится понятие кучи Леонардо - это неявная структура, содержащая L[k] элементов. Для k=0 и k=1 она состоит из единственного элемента. Для k > 1 куча Леонардо состоит из трех идущих подряд частей: кучи размером L[k-1], кучи размером L[k-2] и одного элемента, называемого корнем (для кучи из одного элемента ее корнем считается он сам). Такая куча имеет размер L[k-1] + L[k-2] + 1 = L[k], т.е. ее устройство повторяет определение чисел Леонардо. Структура неявная, т.е. все элементы лежат подряд в обычном массиве, просто мы мысленно разбиваем их на группы и интерпретируем как кучи.

Не каждое натуральное число является числом Леонардо, но любое натуральное число можно представить в виде суммы чисел Леонардо с разными индексами. Мы это вскоре докажем конструктивно в нашем коде. Поэтому массив любой длины мы можем "покрыть" идущими подряд кучами Леонардо с убывающими индексами (k). Такую последовательность кучек Леонардо назовем рядом куч (heap string). И чтобы это все было не зря, потребуем от этих неявных структур несколько свойств. Свойством кучи Леонардо (heap property) сделаем требование, чтобы ее корень всегда был не меньше, чем корни двух входящих в нее подкуч (если они есть). А свойством ряда куч (heap string property) сделаем требование неубывания корней кучек, из которых этот ряд состоит. Вот так, например, выглядит ряд куч Леонардо на массиве из 10 элементов:

В этом ряду две кучи, одна имеет размер sz=9, индекс k=4, ее корнем является элемент с индексом r=8. Вторая куча состоит из одного элемента с позицией r=9, имеет размер sz=1 и индекс k=1. Первая куча размером L[4] внутри имеет две подкучи размерами L[3] и L[2], которые содержат подкучи меньшего размера и т.д. Для каждой кучи выполнено ее свойство - корень не меньше корней подкуч, и для всего ряда выполнено свойство ряда - корни входящих в него куч не убывают. В итоге сам массив еще не отсортирован, но уже какой-то порядок в нем есть. Можно заметить, что следствием свойства кучи является тот факт, что среди всех входящих в нее элементов максимальный - самый правый. Такое же следствие и у свойства ряда куч - самый правый элемент больше всех (не меньше, если быть точным).

Отсюда уже несложно угадать устройство smoothsort'a. Мы берем массив и сперва проходимся по нему слева-направо, постепенно с нуля строя ряд куч, добавляя в него по одному элементу и обеспечивая выполнение требуемых свойств. В результате максимальный элемент занимает свое место - самое правое. Потом мы идем обратно справа-налево, выкидывая по элементу и сокращая ряд куч, так же обеспечивая выполнение обоих свойств. Тогда на каждом шагу этого обратного хода максимальный элемент из всех, входящих в ряд, оказывается справа, а ряд на каждом шагу сокращается. В результате, если у нас массив A из n элементов, то после построения ряда из n элементов у нас элементы A[0..n-2] не больше, чем A[n-1], потом после сокращения ряда на 1 элементы A[0..n-3] не больше, чем A[n-2], ..., и когда ряд сократится до двух элементов, получится, что A[0] <= A[1] <= A[2] ... <= A[n-1], т.е. массив окажется отсортированным. Всего будет почти 2n шагов, каждый из которых потребует не более O(log n) действий, что доказано elsewhere (любимое слово Hongwei Xi), поэтому общая сложность O(n * log n).

Неупомянутые детали. Каким образом происходит рост ряда куч при добавлении одного элемента? Мы смотрим на две самых правых кучи. Если их индексы - соседние убывающие числа (например, 3 и 2 или 1 и 0), то из двух этих куч и нового элемента мы делаем одну кучу большего размера. В противном случае просто добавляем кучу из одного этого нового элемента, а индексом ее считаем 0, если последняя куча в ряду имела индекс 1 (чтобы получились подряд идущие кучи с индексами 1 и 0), и 1 иначе. Сокращение ряда на один элемент еще проще. Если самая правая куча имеет размер 1, то просто ее выкидываем. Если ее размер больше 1, то она состоит из двух подкуч и корневого элемента. Ставим вместо нее в ряд две эти подкучи, а корневой элемент оказывается выкинутым. После каждой такой операции нужно восстановить требуемые свойства, об этом позже.

Теперь давайте запишем это все на ATS. Мы хотим описать функцию с таким заголовком:
fn {a:t@ype} smoothsort {n:nat | n > 0} 
 (A : array(a,n), n : int n, gt : gt(a)): (SORTED(0,n-1) | void) 

где
typedef gt (a:t@ype) = (a,a) -> bool // x > y

Для любого типа данных a наша полиморфная функция smoothsort принимает массив А из n элементов типа a, количество элементов n и функцию сравнения элементов gt, возвращающую true, когда первый аргумент больше второго. Функция сортирует массив на месте, и ничего не возвращает (void), а вместе с этим ничего она возвращает доказательство утверждения SORTED(0, n-1), т.е. что элементы массива с индексами от 0 до n-1 включительно теперь отсортированы. Будем считать, что сортируем массив по возрастанию, сортировка по убыванию получается просто передачей на вход другой функции сравнения.

Акт 2. Вспомогательные теоремы


Как определить утверждение о сортированности массива или его части? Для этого нам потребуются утверждения об отношениях между отдельными элементами. Поскольку во всем алгоритме мы оперируем одним единственным массивом элементов А, то мы не будем его включать в определения утверждений и доказательств, мы будем просто понимать, что все эти утверждения именно о нем. Определим для начала утверждение о том, что один элемент нашего массива не больше другого (Less Than or Equal):
// LTE(i,j) means A[i] <= A[j]
dataprop LTE(int, int) = 
  | {i,j:nat} LTEcompared(i,j)
  | {k:nat} LTErefl(k,k)

Описание утверждения похоже на описание алгебраического типа данных, только его конструкторы строят не данные, а доказательства. Отношение "меньше или равно" рефлексивно: для любого элемента с индекcом k этот элемент не больше сам себя, и мы можем сконструировать этот факт (LTErefl "типа" LTE(k,k) ) из ничего. Для двух же произвольных элементов мы можем их сравнить между собой переданной нам функцией сравнения и произвести факт-доказательство того, что один не больше другого. Например, можно описать такую функцию упорядочивания пары элементов:
//returns true if a swap occured
fn {a:t@ype} order_elements {n,i,j:nat | j < n; i <= j}
  (A: array(a,n), i : int i, j : int j, gt : gt(a)): (LTE(i,j) | bool) = 
    if A[i] \gt A[j] then 
      let val tmp = A[i] in A[i] := A[j]; A[j] := tmp;      
      (LTEcompared | true)
    end 
    else (LTEcompared | false)  

Она принимает массив А из n элементов, индексы i и j, такие что первый не больше (левее) второго и оба меньше n, функцию сравнения gt, и упорядочивает элементы A[i] и A[j], возвращая признак того, произошла ли перестановка, а также доказательство утверждения LTE(i,j), т.е. что теперь A[i] <= A[j]. У нас будет всего пара таких простых функций, производящих сравнение элементов и конструирующих доказательства утверждений о том, что один элемент не больше другого. В силу тривиальности этих функций их корректность легко проверить вручную. Вызов этих функций будет единственным способом получить такие доказательства, и также единственным местом, где будут производиться какие-либо изменения в нашем массиве. Это даст нам уверенность в том, что утверждения об элементах массива действительно соответствуют фактическому положению дел, несмотря на то, что непосредственно их доказательства не включают в себя реальные значения из массива, т.е. связь между ними не железобетонная.

Отношение "меньше или равно" транзитивно, этот факт можно описать аксиомой:
praxi lte_trans {a,b,c:nat} (ab : LTE(a,b), bc : LTE(b,c)): LTE(a,c)

Она гласит: если у нас есть доказательство ab утверждения LTE(a,b) (т.е., что A[a] <= A[b]) и доказательство bc утверждения LTE(b,c), то справедливо утверждение LTE(a,c), т.е. A[a] <= A[c]. lte_trans - функция, принимающая два факта-доказательства и возвращающая новый факт-доказательство. Ключевое слово praxi говорит, что это аксиома, и тела функции (конструктивного доказательства) не будет.

Имея утверждения об упорядоченности элементов, можно определить понятие отсортированности куска массива (по возрастанию). Кусок из единственного элемента всегда отсортирован, это тривиальный случай, служащий базой индукции. Если доказано, что кусок массива A[i..j] отсортирован, и доказано, что A[i-1] <= A[i], то учитывая транзитивность сравнения, кусок массива A[i-1 .. j] тоже отсортирован по возрастанию. Записывается это так:
//SORTED(i,j) means A[i..j] is sorted
dataprop SORTED(int, int) =
  | {k:nat} SORTEDsingle(k,k)
  | {i,j:nat | i > 0; i <= j} SORTEDjoin(i-1, j) of (LTE(i-1,i), SORTED(i,j))

Наша функция сортировки должна будет построить конструктивно доказательство утверждения SORTED(0, n-1). Поскольку мы не определили такое утверждение для пустых массивов, а только начиная с одного элемента, наша функция сортировки будет принимать только непустые массивы, что отражено в ее заголовке.

В процессе доказательства нам пригодится еще один вид утверждений: о том, что в некотором куске массива самый правый его элемент не меньше других (максимален), это то свойство, которым обладают правильные кучи Леонардо, а также правильный ряд куч. Устроено оно похожим образом. Для куска из одного элемента это свойство выполнено автоматически. А если у нас доказано, что среди элементов A[i..j] максимальный элемент самый правый, и что A[i-1] <= A[j], то и среди элементов A[i-1 .. j] тоже самый правый элемент максимальный.
// MAXRIGHT(i,j) means A[j] is the max of A[i..j]
dataprop MAXRIGHT(int, int) =
  | {k:nat} MRsingle(k,k)
  | {i,j:nat | i <= j; i > 0} MRgrow_l(i-1,j) of (LTE(i-1,j), MAXRIGHT(i,j))

Нам также пригодятся несколько простых вспомогательных теорем.
Во-первых, если среди элементов A[i..j] самый правый максимальный, то он не меньше любого из элементов A[i..j]. Для нас это очевидно, а компилятору нужно предъявить явное доказательство:
prfun lte_from_maxright {i,j,k:nat | i <= k; k <= j} .<j-i>. 
  (mr: MAXRIGHT(i,j), k: int k, i: int i, j: int j): LTE(k,j) =
    case+ mr of
    | MRsingle () => LTErefl
    | MRgrow_l(lte_ij, mr_i1j) => 
        if k = i then lte_ij else lte_from_maxright(mr_i1j, k, i+1, j)

Ключевое слово prfun определяет рекурсивную функцию-доказательство, производящую факт-доказательство некоторого утверждения. В данном случае функция lte_from_maxright принимает доказательство mr утверждения MAXRIGHT(i,j), сами числа i и j, а также индекс k, заключенный между ними, и возвращает доказательство утверждения LTE(k,j), т.е. что A[k] <= A[j]. Тело функции-теоремы состоит из анализа случаев того, чем может быть mr. Паттерн-матчинг то бишь. Знак "+" после case означает обязательную проверку на exhaustiveness анализа, чтобы компилятор выдал ошибку, если мы какой-то возможный случай забыли рассмотреть. Фактически, доказательство MAXRIGHT(i,j) состоит из тривиального факта о j-том элементе и множестве фактов LTE(x,j). Перебирая их один за одним, мы найдем тот, где x будет равен k, это будет искомый факт. Ежели факт mr представлен вариантом MRsingle, то это тривиальный случай куска из единственного элемента, и мы можем применить рефлексивность нашего отношения и использовать LTErefl. Если же mr представлен вариантом MRgrow_l, т.е. построен из доказательств утверждений LTE(i,j) и MAXRIGHT(i+1, j), которые мы посредством паттерн-матчинга поименовали lte_ij и mr_i1j, то ежели k = i, то значит LTE(i,j) есть LTE(k,j), искомое утверждение, и имеющийся факт lte_ij его доказывает, его-то и вернем. А если i еще не равно k, то рекурсивно вызываем себя для mr_i1j : MAXRIGHT(i+1, j).

Рекурсивные функции-теоремы обязаны завершаться, ведь если такая функция зациклится, то нужное значение так и не найдет, и значит теорема не доказана. Поэтому тут требуется указать метрику завершимости - уменьшающееся при каждом рекурсивном вызове натуральное число. В данном случае это j - i, т.к. j не меняется, а i растет.

Функции-теоремы очень похожи на обычные функции, просто вместо данных там факты, а вместо их типов - утверждения, которые те доказывают. Однако главное отличие в том, что функции-теоремы используются только в момент компиляции, в конечный код они не попадают. Причем, они вообще никогда на самом деле не исполняются, им ведь главное типизироваться и не содержать ошибок. Если все варианты рассмотрены, и в каждом варианте все статические условия выполнены, то функция корректна, теорема доказана. Писать код, старательно доказывать компилятору его корректность, осознавая, что он все равно превратится в no-op, - не это ли благородное недеяние, воспетое древними мудрецами? Нет, не это. Но близко.

Еще одна теорема, которая нам пригодится. Если у двух подряд идущих кусков массивов максимальные элементы самые правые, и из этих двух локальных максимумов правый не меньше левого, то в силу транзитивности сравнения мы можем объединить два куска массива в один, и в нем тоже максимальный элемент будет самым правым. Записывается доказательство такой теоремы так:
prfn mr_join {a,b,c:nat | a <= b; b < c}
  (ab: MAXRIGHT(a,b), bc: MAXRIGHT(b+1, c), lte_bc: LTE(b,c), 
    a: int a, b: int b): MAXRIGHT(a,c) = let 
  prfun loop {i:nat | i >= a; i <= b+1} .<i-a>. (i: int i, mr_ic: MAXRIGHT(i,c)): MAXRIGHT(a,c) =
    if i=a then mr_ic else
    loop(i-1, MRgrow_l(lte_trans(lte_from_maxright(ab, i-1, a, b), lte_bc), mr_ic))    
in
  loop(b+1, bc)
end

Строится доказательство в цикле. Берем факт bc о том, что у правого куска (A[b+1..c]) самый правый элемент максимален, и по-одному элементу расширяем его влево: каждый очередной элемент из левого куска A[a..b] не больше A[b] (тут мы используем предыдущую теорему lte_from_maxright), а тот не больше, чем A[c] (это дано в условии в виде факта lte_bc: LTE(b,c) ), поэтому используя аксиому о транзитивности lte_trans получаем доказательство LTE(i-1, c), его прилепляем слева к текущему и получаем MAXRIGHT(i-1,c), пока не получим MAXRIGHT(a,c), который и требовалось получить.

Ну и еще одна теорема, простое следствие из предыдущей. Если в некотором куске массива A[a..b] самый правый элемент максимален, а следующий за ним элемент A[b+1] еще больше, то среди элементов A[a..b+1] самый правый максимальный.
prfn mr_grow_r {a,b:nat | a <= b}
  (ab: MAXRIGHT(a,b), pf_lte: LTE(b,b+1), a: int a, b: int b): MAXRIGHT(a,b+1) = let
  prval b1 : MAXRIGHT(b+1,b+1) = MRsingle
in
  mr_join(ab, b1, pf_lte, a, b)
end    

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

Акт 3. Структуры с гарантиями


Для реализации алгоритма нам потребуется хранить информацию о текущем устройстве ряда куч Леонардо в массиве. Некоторые реализации используют для этого битовый вектор, вычисляя необходимые параметры на ходу, но мы будем использовать более явную, наглядную и подробную структуру. Одна куча Леонардо у нас будет описываться структурой из трех чисел, ее характеризующих: индекс k, размер sz, равный L[k], и позиция корневого элемента в массиве, определяющая расположение кучи. Ряд куч будет описываться односвязным списком таких структур. Кроме данных структуры будут включать в себя факты об этих данных, это обеспечит корректность структур, ведь далеко не любая тройка чисел описывает правильную кучу Леонардо. Требуя предоставить доказательства, компилятор не даст нам создать неправильную структуру. Например, факт того, что sz=L[k], будет выражен явно доказательством утверждения LEON(k,sz). Само это утверждение определено так (мы это уже делали в прошлых сериях):
dataprop LEON(int, int) = //LEON(n,x) means L[n] = x
| {n:nat | n <= 1} LEONbase(n, 1)
| {n:nat | n > 1; a,b:nat | a > 0; b > 0} LEONind(n, a+b+1) of (LEON(n-2, a), LEON(n-1,b))

Кроме того, нам надо связать между собой значения root (позиция корневого элемента) и sz (размер кучи): root не может быть меньше, чем sz-1, а sz не может быть меньше 1. Опишем такое утверждение:
dataprop LH(int, int) = //LH(r,sz) means r >= sz - 1, r>=0, sz>=1
  {r,sz:nat | sz >= 1; r >= sz - 1} LHpf(r,sz)

Задание предусловий перед конструктором не позволит его использовать, если условия не выполнены. Имея доказательство LHpf утверждения LH(r,sz), мы можем "извлечь" эти условия:
prfn lh_use {r,sz:int} (pf : LH(r, sz)): [r >= sz-1; sz >= 1] void =
  case pf of LHpf () => ()

Здесь функция-теорема lh_use возвращает "ничто", void, снабженное несколькими статическими неравенствами. Тело ее состоит из "анализа вариантов": если передан такой-то конструктор, то его предусловия были выполнены, а значит они истинны и сейчас.

Итак, куча Леонардо, имеющая правильную форму, но для которой необязательно выполнено свойство кучи (про корень), будет описываться так:
typedef heap(r:int, k:int, sz:int) = 
  @{root = int r, k = int k, sz = int sz, pf_sz = LEON(k,sz), pf_r = LH(r,sz), pf_gc = GOODCHILDREN(k)}

Она состоит из трех целых чисел и доказательств некоторых утвеждений о них. Последнее из утверждений будет описано чуть позже.

Если для кучи выполнено ее свойство (корень не меньше корней подкуч), то корень ее является не только самым правым элементом, но и максимальным. Структуру, состоящую из кучи Леонардо и доказательства выполненности этого свойства, назвомем heap1:
typedef heap1(r:int, k:int, sz:int) = 
  @{ hp = heap(r,k,sz),  pf_mr = MAXRIGHT(r-sz+1, r) }

Если для ряда куч выполнено свойство ряда (корни не убывают), то для каждой кучи в нем справедливо утверждение, что ее корень - самый правый и максимальный элемент всего ряда от начала до текущей кучи. Добавим в структуру доказательство такого факта, получим heap2:
typedef heap2(r:int, k:int, sz:int) = 
  @{ hp = heap(r,k,sz),  pf_mr = MAXRIGHT(r-sz+1, r), pf_totalmr = MAXRIGHT(0,r) }

Именно из таких структур будет состоять список, определяющий правильный ряд куч Леонардо. Но в такой список нельзя добавлять что попало (например, один и тот же элемент), между позициями корней соседних элементов есть вполне определенная связь: они отличаются ровно на размер очередной кучи. Если m - общее число элементов в ряде куч, то ряд можно описать так:
datatype heaps(int) = 
  | heaps_nil (0)
  | {m,r,k,sz:nat | m + sz - 1 == r} heaps_cons (r+1) of (heap2(r,k,sz), heaps(m))

#define :: heaps_cons
#define nil heaps_nil

В пустом ряду 0 элементов. Если есть ряд из m элементов и куча (со всеми свойствами) из sz элементов, то ее корень должен иметь быть на позиции m + sz - 1. Только если это условие выполнено, мы сможем добавить очередную кучу в наш ряд. Ряд куч представлен "списком", который синтаксически "растет" влево (как обычно это делают списки в функциональных языках), но логически в массиве ряд растет вправо: голова списка является последней, самой правой кучей.

Акт 4. Еще о числах Леонардо


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

Чтобы из двух куч и нового элемента сделать одну большую, нужно из доказательств о том, что размеры тех куч являются числами Леонардо, сделать доказательство, что размер новой тоже является нужным числом Леонардо. Для этого используется конструктор LEONind, но в его предусловиях требуется положительность значений. Ее можно извлечь из фактов-аргументов с помощью такой теоремы:
prfn leon_positive {n,x:nat}  (pf : LEON(n,x)): [x > 0] void = 
  case+ pf of
  | LEONbase () => ()
  | LEONind(pf1, pf2) => ()

Вновь анализ вариантов и использование информации о соответствующих вариантам предусловиях. Выглядит функция-доказательство так, будто в ней ничего не делается, однако ж делается, просто за кадром. Еще забавнее это выглядит в следующей теореме:
prfun leon_isfun {n,x1,x2:nat} .<n>. (pf1 : LEON(n,x1), pf2 : LEON(n,x2)): [x1==x2] void =
  case+ (pf1, pf2) of
  | (LEONbase (), LEONbase ()) => ()
  | (LEONind(p1a,p1b), LEONind(p2a,p2b)) => let
      prval () = leon_isfun(p1a, p2a)
      prval () = leon_isfun(p1b, p2b)
    in () end

Тут мы доказываем, что L[k] - функция в математическом смысле, т.е. Если x=y, то L[x]=L[y]. Теорема получает два факта (LEON(n,x1) и LEON(n,x2)) и доказывает, что x1==x2. Опять разбор вариантов и сопоставление аргументов. Если оба факта - LEONbase, то имеют "тип" LEON(n,1), т.е. x1=1 и x2=1, а значит x1==x2. Если оба - LEONind, то содержат вполне определенные доказательства, которые можно сопоставить друг с другом рекурсивно. А разные конструкторы pf1 и pf2 иметь не могут, т.к. у разных конструкторов взаимоисключающие предусловия на n, так что нерассмотренных вариантов в case+ не возникает, и компилируется такое доказательство без ошибок. Используется оно в теореме о монотонности L[x]:
prfn leon_mono {k1,sz1,sz2:nat | k1 > 0} 
  (pf1 : LEON(k1, sz1), pf2 : LEON(k1 + 1,sz2)): [sz1 < sz2-1] void = let
    prval LEONind(pf2_2, pf2_1) = pf2
    prval () = leon_isfun(pf1, pf2_1)
  in () end

Тут доказывается, что после единицы каждое следующее число Леонардо больше предыдущего (как минимум на 2). Что тут происходит: В предусловии сказано, что k1 > 0, значит k1 + 1 > 1, значит pf2 имеет вид LEONind(...), и паттерн-матчингом в первой строке мы извлекаем входящие в него доказательства, которые назовем pf2_2 и pf2_1. Если вспомнить определение LEONind, видно, что они имеют "тип" LEON(k1 + 1 - 2, x1) и LEON(k1 + 1 - 1, x2) соответственно, где x1 и x2 - некоторые натуральный числа. Получается, что у pf2_1 и исходного аргумента pf1 похожий "тип" - они оба LEON(k1, ...), т.е. утверждения о числе L[k1]. Теперь мы задействуем теорему leon_isfun, которая устанавливает равенство значений L[k1], т.е. sz1==x2. Но из определения LEON(k1 + 1,sz2) компилятор знает, что sz2 = x1 + x2 + 1, а значит sz2 = x1 + sz1 + 1, где x1 - некоторое натуральное число, а значит sz1 < sz2-1, ч.т.д.

Еще одна похожая теорема: если для натуральных чисел k,sz, sz1 и sz2, таких что k > 1 и sz2 == sz - 1 - sz1, у нас есть доказательство, что L[k]=sz, а L[k-1]=sz1, то L[k-2]=sz2. Это нужно при вычислении размеров подкуч: если мы знаем размер большой кучи L[k] и вычислили размер одной из подкуч L[k-1], то размер второй подкучи L[k-2] можем получить вычитанием.
prfn leon_size_dif {k,sz, sz1, sz2 : nat |  k > 1; sz2 == sz - 1 - sz1}    
  (pfsz : LEON(k,sz), pfsz1 : LEON(k-1,sz1)) : LEON(k-2, sz2) =
  case+ pfsz of 
  | LEONind(pf_2, pf_1) => let prval () = leon_isfun(pf_1, pfsz1) in pf_2 end

Способ доказательства практически идентичен предыдущей теореме.

Ну и еще одна мелочь, которая потребуется в коде: доказательство того, что для k < 2 L[k]=1.
prfn leon_base_is1 {k,sz:nat | k < 2} (pf : LEON(k,sz)): [sz==1] void =
  case+ pf of LEONbase () => ()

Анализ вариантов исчерпывается единственным случаем в силу условия на k, и из определения LEONbase компилятор извлекает требуемый факт.

Продолжение...

02 февраля 2012, 07:53

ATS и smoothsort: продолжение

Акт 5. Мемоизация доказательств


Для обеспечения коррекности структур и убеждения компилятора в том, что мы не делаем ничего предосудительного, у нас используются доказательства утверждений LEON(k,sz) о том, что sz - действительно k-е число Леонардо, L[k]=sz. В одном из прошлых постов этой серии мы видели, как построить функцию, вычисляющую нужное число Леонардо и поставляющую вместе с ним доказательство того, что это правильное число. Там было несколько вариантов, и лучший из них имел сложность O(k). Если такую функцию задействовать в smoothsort'e, его сложность резко возрастет и из O(n * log n) превратится во что-то вроде O(n * log n * log n). Нужна мемоизация, и оригинальный алгоритм подразумевает использование массива с уже вычисленными числами Леонардо, так получение нужного числа отнимает O(1) операций. Но такой массив имеет размер опять же O(log n), поэтому рассказы про потребление памяти О(1) - враки. Попытка добавить мемоизацию в функцию вычисления чисел Леонардо сталкивается с проблемой. Функция определена как
fun leon {n:nat} (n : int n) : [x:nat] (LEON(n,x) | int x) 

тип ее результата зависит от значения аргумента. Зависимые типы, понимаш. Если мы захотим сохранить результаты в массиве, то каждый его элемент будет иметь свой тип! А так нельзя - в массивах все элементы одного типа. Не знаю, как эту проблему решают во взрослых языках с зависимыми типами, а в ATS можно воспользоваться хаком. Сперва дадим типу результата функции leon свое имя:
typedef L(n:int) = [x:nat | x > 0] (LEON(n,x) | int x)

Определим интерфейс для функций сохранения вычисленных значений в массиве-кэше и извлечения оттуда.
extern fun leon_cache_get {n:nat} (n: int n): Option(L(n)) = "leon_cache_get"
extern fun leon_cache_set {n:nat; x:int} (n: int n, v: L(n)): void = "leon_cache_set"

А теперь опишем похожий тип, но без параметра. L0 - "существует натуральное число n, для которого L(n)". И опишем аналогичные функции с этим типом.
typedef L0 = [n:nat] L(n)
extern fun leon_cache_get0 {n:nat} (n: int n): Option(L0) = "leon_cache_get"
extern fun leon_cache_set0 {n,x:nat} (n: int n, v: L0): void = "leon_cache_set"

Теперь тип результата не зависит от значения, и мы можем сохранить результат в массиве:
#define LSIZE 43
val Lcache : array(Option(L0), LSIZE) = array_make_elt(LSIZE, None) 
implement leon_cache_get0(n) = if n < LSIZE then Lcache[n] else None 
implement leon_cache_set0(n, v) = if n < LSIZE then Lcache[n] := Some v else ()

И тут срабатывает хак: когда мы после декларации функции написали = "leon_cache_get", мы задали имя функции на Си, в которую та компилируется. Задав обоим вариантам функций работы с кэшем одинаковые Си-имена, мы сделали так, что реализация leon_cache_get0 является одновременно и реализацией leon_cache_get. Компилятор это устраивает, т.к. при трансляции в Си вся шелуха статических условий и деталей исчезает, остаются только реальные данные, а они в обоих вариантах совпадают - на входе инт и на выходе инт. В результате мы получили функции для сохранения и извлечения вычисленных значений зависимых типов за О(1), что и хотелось. С ними функция вычисления чисел Леонардо с мемоизацией выглядит так:
fun leon {n:nat} (n: int n): L(n) = 
  if n < 2 then (LEONbase | 1)
  else 
    case+ leon_cache_get n of 
    | Some x => x
    | None () => let
        val (pa | a) = leon(n-2)
        val (pb | b) = leon(n-1)
        val res = (LEONind(pa, pb) | a + b + 1)        
      in
        leon_cache_set(n, res);        
        res
      end  

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

Акт 6. Метаморфозы


Выше мы определили три типа структур, описывающих кучу Леонардо, отличающиеся тем, какие свойства для кучи выполнены (и доказаны). Если куча просто имеет правильную форму и положение, то это heap(r,k,sz), где r - позиция корня, k - индекс (номер числа Леонардо) и sz - размер. Если для нее доказано утверждение MAXRIGHT(r-sz+1, r), т.е. корневой элемент является максимальным во всей куче (выполнено свойство кучи), то это heap1. Когда же эта куча - звено правильного ряда куч, для которого выполнено свойство ряда, выраженное как MAXRIGHT(0,r), то это heap2. Ряд куч описан типом heaps(m), где m - суммарное число элементов в нем, и является списком из heap2. В первой части алгоритма мы в цикле поэлементно растим кучу от пустого списка до heaps(n), где n - размер массива, путем либо добавления новой кучи из одного элемента, либо объединения двух последних куч в одну с добавляемым элементом в качестве корня.

Маленькую кучу из одного элемента будет создавать функция small_heap:
fn small_heap {r:nat} (r:int r, prev_k : int):<> [k:two] heap(r,k,1) =
  if prev_k = 1 then #[.. | @{root=r, k=0, sz=1, pf_sz=LEONbase, pf_r = LHpf, pf_gc = GCsmall}]
  else #[.. | @{root=r, k=1, sz=1, pf_sz=LEONbase, pf_r = LHpf, pf_gc = GCsmall}]

Ее индекс - 0 или 1 в зависимости от индекса предыдущей кучи. Поскольку размер ее равен 1, доказательства утверждений про размер тривиальны (используются базы индукции). Синтаксическая конструкция #[.. | some data ] здесь превращает значение типа heap(r,k,1) в значение типа [k:two] heap(r,k,1), т.е. добавляет статической информации, в данном случае о том, что k:two, т.е. k:nat и k < 2. Эту конструкцию вы не найдете в документации на язык, это пример тайного знания, передаваемого лично от мастера к ученику.

Для кучи из одного элемента свойство кучи выполнено автоматически. Теперь, чтобы ее можно было добавить в список-ряд, нужно превратить ее в heap2, т.е. обеспечить выполненность свойства ряда, для чего может потребоваться переставить местами какие-то элементы. Свойства кучи и ряда формулируются через отношения между корнями куч, поэтому для выполнения этих свойств нужно обеспечить правильный порядок среди корней, т.е. меняться местами будут только корни каких-то куч. Если у большой кучи, для которой было выполнено свойство кучи, меняется значение в ее корне, то она теряет свое свойство, но две входящих в нее кучи поменьше не затронуты, они свои свойства сохраняют. Чтобы восстановить свойство кучи, нужно сравнить новое значение корня с корнями подкуч и при необходимости произвести перестановку, для этого нужно эти подкучи получить в виде нужных структур. Наши структуры для кучи не содержат подкучи в явном виде, иначе объем требуемой памяти под них был бы заметно больше, чем сам сортируемый массив. Поэтому нам нужна функция извлечения из большой кучи двух ее подкуч. Поскольку большие кучи мы всегда строим из правильных куч с выполненными свойствами, то при извлечении подкуч они должны быть с выполненными свойствами, т.е. они должны иметь тип heap1, содержащий доказательство MAXRIGHT(r-sz+1, r). Но откуда взять это доказательство? Из аналогичного MAXRIGHT для большой кучи оно не следует. Тут мы немного срежем угол и зададим нужное свойство аксиомой. При сборке большой кучи из маленьких мы потребуем выполненности свойств кучи для обеих подкуч. Для этого в определение кучи добавлено утверждение GOODCHILDREN. Для маленькой кучи оно выполнено бесплатно, это говорит конструктор GCsmall, определенный для k=0 и k=1 без пререквизитов.
// a heap is either a single element or contains proper sub-heaps
dataprop GOODCHILDREN(int) =
  | {k:two} GCsmall(k)
  | {k:nat | k > 1; a,b,c:nat} GCbig(k) of (MAXRIGHT(a,b), MAXRIGHT(b+1,c))

extern praxi good_children_mr {a,b,c,d:nat} {k:nat | k > 1} 
  (pf : GOODCHILDREN(k)): (MAXRIGHT(a,b), MAXRIGHT(c,d))

extern praxi good_child {d:nat | d < 3} {k:nat | k > 1} (pf : GOODCHILDREN(k)): GOODCHILDREN(k-d)

А при разделении кучи и извлечении из нее подкуч выполненность свойств кучи для них будет следовать из доказательства GOODCHILDREN для большой кучи аксиоматично. Т.е. все честно, и при разделении большой кучи доказанное свойство кучи для подкучи может быть получено только если оно было выполнено при создании этой большой кучи. Тогда функция разделения большой кучи и извлечения подкуч выглядит так:
fn split_heap {r,k,sz:nat | k > 1} (h: heap(r,k,sz)):
    [cr1,csz1, cr2,csz2:nat | cr1 - csz1 == r - sz; cr2 == r-1; cr2 == cr1 + csz2; csz1 > 0; csz2 > 0 ] 
    (heap1(cr1, k-1, csz1), heap1(cr2, k-2, csz2)) = let
  val (pfsz1 | child_size1) = leon(h.k - 1)
  prval () = lh_use h.pf_r
  prval () = leon_mono(pfsz1, h.pf_sz)    
   
  val child_size2 = h.sz - 1 - child_size1
  prval pfsz2 = leon_size_dif(h.pf_sz, pfsz1)
    
  prval pfgc1 = good_child {1} h.pf_gc
  prval pfgc2 = good_child {2} h.pf_gc    
    
  val left_h =  @{root = h.root - h.sz + child_size1, k = h.k - 1, sz = child_size1, 
            pf_sz = pfsz1, pf_r = LHpf, pf_gc = pfgc1}
    
  val right_h = @{root = h.root - 1, k = h.k - 2, sz = child_size2,
            pf_sz = pfsz2, pf_r = LHpf, pf_gc = pfgc2}   
    
  prval (pfmr1, pfmr2) = good_children_mr h.pf_gc
in #[.. | (@{hp=left_h, pf_mr=pfmr1}, @{hp=right_h, pf_mr=pfmr2})]
end

Она из большой кучи типа heap получает две ее подкучи типа heap1. Размер первой подкучи вычисляется через числа Леонардо, размер второй - вычитанием размера первого из размера большой кучи минус 1. Доказательство того, что это нужное число Леонардо нам дает доказанная выше теорема leon_size_dif. Доказательство того, что новый корень не окажется за пределами массива, дают теоремы lh_use (про связь корня и размера большой кучи) и leon_mono (про то, что размер подкучи меньше размера большой кучи, т.к. это предыдущее число Леонардо). Доказательства свойств кучи для подкуч дает аксиома good_children_mr. Помимо самих структур для подкуч функция поставляет статическую информацию о том, как связаны в них значения, в виде пяти равенств и неравенств.

Теперь, если наш ряд состоит из двух куч с соседними убывающими индексами, и мы добавляем новый элемент, то из этих двух куч и элемента мы легко можем сделать одну большую кучу типа heap, т.е. имеющую правильную форму, имеющую правильных детей (GOODCHILDREN), но пока еще без выполненности свойства кучи. Аналогично, если у нас была хорошая куча со всеми свойствами, но у нее поменялось значение в корне, то опять она стала простой heap, и нужно восстановить свойство кучи. Для восстановления свойства кучи и получения структуры типа heap1 нам потребуются такие функции:
datatype compare_res(int,int) =    
  | {i,j:nat} LeftGr(i,j) of (LTE(j,i) | int)
  | {i,j:nat} RightGr(i,j) of (LTE(i,j) | int)
  
fn {a:t@ype} compare_elements {n,i,j:nat | j < n; i <= j}
  (A: array(a,n), i : int i, j : int j, gt : gt(a)) : compare_res(i,j) =
    if A[i] \gt A[j] then LeftGr (LTEcompared | 0)
    else RightGr (LTEcompared | 1)

fun restore_heap_prop {r,k,sz:nat | r < n} (h: heap(r,k,sz)):<cloref1> heap1(r,k,sz) = 
  if h.k < 2 then 
    let prval () = leon_base_is1 h.pf_sz in @{hp = h, pf_mr = MRsingle} end
  else let
    val (left, right) = split_heap h in
    restore_big_heap_prop(left, right, h)
  end
  
and restore_big_heap_prop  {r,k,sz, cr1,csz1, cr2,csz2:nat | r < n; k > 1; 
  cr1 - csz1 == r - sz; cr2 == r-1; cr2 == cr1 + csz2} 
  (left: heap1(cr1, k-1, csz1), right: heap1(cr2, k-2, csz2), h: heap(r,k,sz)):<cloref1> heap1(r,k,sz) = let       
    val start = left.hp.root - left.hp.sz + 1
    prval () = lh_use left.hp.pf_r
    prval () = leon_positive right.hp.pf_sz 
  in
    case+ compare_elements(A, left.hp.root, right.hp.root, gt) of
    | LeftGr(pf_lte_rl | _) => let
        val (pf_lte_lh | swapped) = order_elements(A, left.hp.root, h.root, gt)          
        val rstart = right.hp.root - right.hp.sz + 1
        prval pf_lte_rh = lte_trans(pf_lte_rl, pf_lte_lh)          
        prval pf_mrr = mr_grow_r(right.pf_mr, pf_lte_rh, rstart, right.hp.root)  
      in
        if swapped then let
            val left1 = restore_heap_prop left.hp
            prval pf_mrheap = mr_join(left1.pf_mr, pf_mrr, pf_lte_lh, start, left1.hp.root)
          in @{hp = h, pf_mr = pf_mrheap}
        end else let
            prval pf_mrheap = mr_join(left.pf_mr, pf_mrr, pf_lte_lh, start, left.hp.root)
          in @{hp = h, pf_mr = pf_mrheap}
        end
      end
    | RightGr(pf_lte_lr | _) => let
        val (pf_lte_rh | swapped) = order_elements(A, right.hp.root, h.root, gt)
      in        
        if swapped then let
            val right1 = restore_heap_prop right.hp 
            prval pf_mr_leftright = mr_join(left.pf_mr, right1.pf_mr, pf_lte_lr, start, left.hp.root)
            prval pf_mrheap = mr_grow_r( pf_mr_leftright, pf_lte_rh, start, right1.hp.root)
          in @{hp = h, pf_mr = pf_mrheap} 
        end else let
            prval pf_mr_leftright = mr_join(left.pf_mr, right.pf_mr, pf_lte_lr, start, left.hp.root)
            prval pf_mrheap = mr_grow_r( pf_mr_leftright, pf_lte_rh, start, right.hp.root)
          in @{hp = h, pf_mr = pf_mrheap}
        end          
      end      
  end // end of restore_big_heap_prop

Функция compare_elements сравнивает два элемента и вместе с результатом поставляет факт-доказательство того, что один элемент не меньше другого. Маленькая куча (с индексом 0 или 1) обладает свойством кучи автоматически, т.к. имеет размер 1, что доказывает теорема leon_base_is1. В большой же куче нужно сравнить между собой корни ее подкуч, и тот, что больше, сравнить с корнем большой кучи. Если корень большой кучи больше максимального из корней подкуч, то он больше их обоих, и из этого факта (получаемого через аксиому о транзитивности lte_trans) и фактов-доказательств свойств кучи подкуч (MAXRIGHT) можно соорудить доказательство свойства кучи для всей большой кучи (используя теоремы mr_grow_r и mr_join). Если же корень большой кучи меньше, то он меняется местами с максимальным из корней подкуч, после чего та подкуча теряет свое свойство и его нужно восстановить, применив рекурсивно ту же самую функцию восстановления свойства. С т.з. доказательств случаи, когда корень левой подкучи больше правого, и когда правый больше левого, не одинаковы, там доказательство свойства кучи получаются немного по-разному, поэтому кода получилось так много. При реализации на языках без доказательств эти случаи неразличимы, и код заметно проще.

Умея восстанавливать свойство кучи, т.е. получать heap1 из heap, можно сделать восстановление свойства ряда. Когда мы к ряду хотим присоединить очередную кучу, у нас есть текущий ряд, для которого свойство ряда
уже выполнено (он состоит из куч типа heap2) и новая куча типа heap, из которой мы хотим получить heap2, которую можно добавить в наш ряд-список типа heaps. При этом могут потребоваться перестановки корней куч, последняя куча ряда может потерять свои свойства и превратиться из heap2 опять в heap, тогда мы получим аналогичную ситуацию уже с меньшим рядом. Восстановлением свойства ряда будут заниматься функции с такими сигнатурами:
fun restore_heapstring_prop {m,r,k,sz:nat | m + sz - 1 == r; r < n} 
  (hs : heaps(m), h:heap(r,k,sz)):<cloref1> heaps(r+1) 
    
and restore_heapstring_prop_small {m,r,k,sz,r1,k1,sz1:nat | m + sz - 1 == r; r1 < n; r1 == r+1; sz1 == 1}
  (hs: heaps(m), h: heap2(r,k,sz), small: heap(r1,k1,sz1)):<cloref1> heaps(r+2) 
        
and restore_heapstring_prop_big {m,phr,phk,phsz,lr,lsz,rr,rsz,hr,hk,hsz:nat | 
  m + phsz - 1 == phr; hr == phr + hsz;  hsz == lsz + rsz + 1; rr + 1 == hr; rr == lr + rsz; 
  lsz > 0; rsz > 0; hr < n;  hk > 1} 
  (hs: heaps(m), prev: heap2(phr,phk,phsz), 
    left: heap1(lr,hk-1,lsz), right: heap1(rr,hk-2,rsz), h: heap(hr,hk,hsz)):<cloref1>  heaps(hr+1) 

Да, пять строк только на описание типа одной функции. Выкинь одно из статических условий, и код не скомпилируется. Тело функций не привожу, там много громоздкого кода. Смысл его в том, что ежели ряд hs пустой, то достаточно восстановить свойство кучи для h, превратив ее из heap в heap1, и свойство кучи для нее одновременно будет и свойством ряда, т.к. она в нем будет одна. Если же в текущем ряду есть хоть одна куча, то в зависимости от размера добавляемой кучи h вызывается либо restore_heapstring_prop_small, либо restore_heapstring_prop_big. Для маленькой кучи нужно ее корень сравнить с корнем предыдущей, при необходимости сделав перестановку и восстановив свойство кучи для предыдущей, в результате можно получить доказательство, что новый корень не меньше всех предыдущих элементов и свойство ряда будет доказано. Для большой же кучи корень последней кучи текущего ряда нужно сравнивать как с корнем добавляемой, так и с корнями ее подкуч, и делать перестановку только если он больше их всех. Иначе достаточно восстановить свойство кучи в добавляемой, тогда ее корень станет не меньше корня последней кучи текущего ряда, и свойство ряда для новой кучи будет выполнено. Полный код можно посмотреть здесь, там все эти случаи аккуратно рассматриваются и нужные доказательства кропотливо конструируются.

Акт 7. Сортировка


Основная функция сортировки будет состоять из "выращивания" ряда от heaps(0) (т.е. nil) до heaps(n), где n - размер массива, и его последующего "сжимания" обратно, попутно формируя доказательство отсортированности, используя доказательства свойств куч ряда. В процессе, глядишь, и массив отсортируется. Выращиваем мы ряд путем вызова grow_loop(0, nil), где
fun grow_loop {i:nat | i <= n} (i : int i, hs : heaps(i)) :<cloref1> heaps(n) =
  if i = n then hs else grow_loop(i+1, grow hs)    

Где функция grow растит ряд на один элемент:
fun grow {m:nat; m < n} (hs : heaps(m)):<cloref1> heaps(m+1) =
  case+ hs of
  | nil () => @{hp = small_heap(0, ~1), pf_mr = MRsingle, pf_totalmr = MRsingle} :: nil        
  | h :: nil () => let 
      val small = small_heap(h.hp.root + 1, h.hp.k)   
    in restore_heapstring_prop_small(nil, h, small)
    end      
  | h0 :: (h0rest as h1 :: rest) => 
      if h0.hp.k + 1 = h1.hp.k then let //join two top heaps into a bigger one
        prval () = lh_use h1.hp.pf_r 
        prval () = leon_positive h0.hp.pf_sz  //prove that child sizes > 0 to use LEONind
        prval pfgc = GCbig(h1.pf_mr, h0.pf_mr)
        val bighp = @{root = h0.hp.root + 1, k = h1.hp.k + 1, sz = h1.hp.sz + h0.hp.sz + 1, 
            pf_sz = LEONind(h0.hp.pf_sz, h1.hp.pf_sz), pf_r = LHpf, pf_gc = pfgc }
        in
        case+ rest of
        | prev :: hps => restore_heapstring_prop_big(hps, prev, heap2to1 h1, heap2to1 h0, bighp)
        | nil () => let 
            val bighp1 = restore_big_heap_prop (heap2to1 h1, heap2to1 h0, bighp) 
          in @{hp = bighp, pf_mr = bighp1.pf_mr, pf_totalmr = bighp1.pf_mr} :: nil
          end          
      end else let //add a small heap
        val small = small_heap(h0.hp.root + 1, h0.hp.k)
        in restore_heapstring_prop_small(h0rest, h0, small)
      end

Здесь паттерн-матчингом по списку куч мы смотрим на две последних (если они есть), и если их индексы - подряд идущие убывающие числа (h0.hp.k + 1 = h1.hp.k), то объединяем их с новым элементом в одну кучу побольше. Иначе добавляем в ряд маленькую кучу из одного элемента.

Кстати, тип функции grow_loop как раз является теоремой о том, что любое натуральное число можно представить суммой чисел Леонардо, а код ее служит доказательством.

Сжиманием непустого ряда на один элемент занимается функция shrink:
fun shrink {m:nat | m > 0; m <= n} (hs : heaps(m)) :<cloref1> heaps(m-1) =
  case+ hs of h :: rest => 
    if h.hp.k < 2 then let prval () = leon_base_is1 h.hp.pf_sz in rest end
    else let   
      val (left,right) = split_heap h.hp
      val hs1 = restore_heapstring_prop(rest, left.hp)          
      in        restore_heapstring_prop(hs1, right.hp)
    end

Если последняя куча имеет индекс меньше 2, то ее размер равен 1 (что доказывает теорема leon_base_is1) и ее можно просто выкинуть, вернув остаток списка. Если же это большая куча, то ее можно разбить на две ее подкучи и корень, две этих кучи добавить в список вместо исходной, а корень выбросить. Добавлением куч занимается restore_heapstring_prop, которая заодно сделает необходимые перестановки и обеспечит выполнение всех требуемых свойств.

Дальше самое главное - цикл скукоживания ряда, в котором мы формируем доказательство отсортированности. Имея непустой ряд из i элементов (hs : heaps(i)), можно взять его последнюю кучу htop типа heap2, она содержит доказательство свойства ряда "типа" MAXRIGHT(0,i-1). Из нее теоремой lte_from_maxright можно получить факт-доказательство LTE(i-2,i-1) того, что A[i-2] <= A[i-1] (когда i больше 1). Если у нас к этому моменту будет доказательство отсортированности элементов A[i-1 .. n-1], то из них мы можем построить доказательство отсортированности A[i-2 .. n-1], которое передать на следующую итерацию цикла. Когда же i = 1, переданное доказательство SORTED(i-1,n-1) и есть SORTED(0,n-1) - искомое доказательство отсортированности всего массива!
fn top {m:int | m > 0}(hs : heaps(m)):<> [r,k,sz:nat | r + 1 == m] heap2(r,k,sz) =  
  case+ hs of h :: rest => h

fun shrink_loop{i:nat | i < n; i > 0} 
  (pf_sorted : SORTED(i-1,n-1) | i : int i, hs : heaps(i)) :<cloref1> (SORTED(0,n-1) | void) =
  if i = 1 then (pf_sorted | ()) 
  else let
    val htop = top hs
    prval pf_lte = lte_from_maxright(htop.pf_totalmr, i-2, 0, i-1)
  in     
    shrink_loop(SORTEDjoin(pf_lte, pf_sorted) | i-1, shrink hs)
  end  


Тогда в теле основной функции smoothsort нужно лишь вызвать grow_loop для выращивания ряда куч, потом сократить его на один элемент, получив необходимые параметры для цикла скукоживания shrink_loop, и вызвать его, получив доказательство отсортированности.
  if n > 1 then let
    val hps = grow_loop(0, nil) //hps : heaps(n)
    val htop = top hps
    prval pf_lte21 = lte_from_maxright(htop.pf_totalmr, n-2, 0, n-1) // LTE(n-2, n-1)
    prval pf_sorted0 : SORTED(n-1, n-1) = SORTEDsingle
    prval pf_sorted1 : SORTED(n-2, n-1) = SORTEDjoin(pf_lte21, pf_sorted0)
    val hps1 = shrink hps
    in shrink_loop(pf_sorted1 | n-1, hps1)
  end else (SORTEDsingle | ())

Все это можно делать, когда в массиве есть хотя бы 2 элемента. А массив из одного элемента и так отсортирован, доказательство этого тривиально - база индукции в SORTED.

Вот так легко и просто можно написать на ATS сортировку smoothsort с доказательством ровно в 400 строк. А теперь обещанный психологический тест. Если вы осилили этот пост и поняли прочитанное, а не просто пролистали, то ваш IQ не меньше 111, и у вас слишком много свободного времени. :)

02 февраля 2012, 07:50

ru_declarative

ru_declarative @ 2011-11-24T11:58:00

давным давно я написал небольшую библиотеку виджетов на ocaml и ncurses. тогда же хотел поделиться, да все руки не доходили...

теперь вот делюсь, может быть кому-то будет полезна: http://hg.smolex.com/ocaml-curses-widgets/summary

что умеет: держать screen, в нем рисовать виджеты, обновлять их содержимое по необходимости, следить за вводом с клавиатуры.
из виджетов есть кнопка, лейбл, чекбокс, комбобокс, поле ввода, несколько диалоговых окон, лист, таблица (правда она так себе, показывает только текст). события, подписка на, все как у взрослых.

там же, рядом, есть небольшой генератор форм. в sexp-ах описывается форма, на нее натравливается генератов, из выхлопа получается форма с виджетом и именем. нужна такая штука из-за того, что у меня не сделаны лайауты, приходится виджеты расставлять руками по координатам и задавать им размеры. с одной стороны не фонтан, да. с другой стороны эта штука писалась для использования во всяком ембедете, чтобы к железке можно было отратиться через rs232 и увидеть +/- вменяемый тпи (текстовый пользовательский интерфейс), там плавать особо нечему, мне генерилки было вполне достаточно.

для сборки нужен elib, тоже собственного производства, лежит рядом, в отдельном репозиратии.

зы. почему не выложил на тот же битбакет? потому что у меня вот прям щас он почему-то недоступен, а если отложу выкладывание на потом, запал может кончиться :)

написал rastafarra 02 февраля 2012, 07:27

Вакансия

Оригинал взят у [info]si14 в Вакансия
Товарищи! Наша компания ищет программистов на Haskell'е на фуллтайм. Личное присутствие в офисе (Петербург, ст.м. Московские Ворота) обязательно. Как обычно, ДМС и прочие плюшки в комплекте. Зарплаты обсуждаем лично. Приходите, поговорим.

UPD: письма лучше писать на office@selectel.ru :)

написал si14 (a2alt@ya.ru) 02 февраля 2012, 07:26

01 февраля 2012

Дмитрий Грошев

Вакансия

Товарищи! Наша компания ищет программистов на Haskell'е на фуллтайм. Личное присутствие в офисе (Петербург, ст.м. Московские Ворота) обязательно. Как обычно, ДМС и прочие плюшки в комплекте. Зарплаты обсуждаем лично. Приходите, поговорим.

UPD: письма лучше писать на office@selectel.ru :)

01 февраля 2012, 18:13

keyfighter

Лисп в первый раз в жизни

Вопрос к лисп-аудитории.

Мне скоро потребуется заменить моего друга на кружке по теории алгоритмов. Рассказывать я могу о чём угодно в течении полутора часов, и я решил рассказать о некоторых философских идеях лиспа для расширения кругозора. Пока что мне приходят в голову две темы для рассказа:
  1. Кодогенерация (макросы, использование программы как данных)
  2. ФП на примере обработки списков (lambda, map, reduce, apply и т.д.)
А какие ещё идеи по вашему мнению стоило бы включить в рассказ?

[update] Если что, то слушатели — десятиклассники, их опыт программирования недалеко выходит за школьный, но есть некоторая стандартная алгоритмическая база.

написал Дож (noreply@blogger.com) 01 февраля 2012, 17:55

darkus

Генерация пожеланий для написания автографов должна выйти на новый уровень

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

1. Переписать автограф на бумагу при помощи своего фирменного почерка, поставить дату и подписаться.

2. Отсканировать все написанные автографы, разделить их по отдельным файлам с изображениями.

3. Включить изображение в текст, сгенерировать на его основе PDF-файл, который необходимо назвать специальным образом.

4. Отправить файл по электронной почте с некоторыми напутственными словами.

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

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

Третья задача решается банально и просто при помощи какого-нибудь сценария пакетной обработки файлов. Четвёртую задачу, в принципе, можно оставить ручной, хотя и тут можно поработать над автоматизацией с использованием всё того же генератора текстов.

Внимание, вопрос. Кто готов заняться этим делом? Например, в рамках выполнения курсовой/практической/дипломной работы.

01 февраля 2012, 05:34

31 января 2012

Codedot

Two-Agents Universality and Observational Equivalence

The idea of Bechet mentioned in «Interaction Combinators» by Lafont appears to be described in «Universal Interaction Systems with Only Two Agents». Although the whole class of such systems is unnaturally complicated, the study of a minimal set of agent types is important with respect to uniform memory implementation of interaction systems taking into account the limitation of basically a single (pseudo-)agent ξ type with two auxiliary ports. The latter directly corresponds to the open question explicitly stated at the end of Bechet's article.

On the other hand, one of the initial goals was also to achieve HP-completeness which is known to be effectively implemented in extensional pure untyped λ-calculus. Mapping of the notion of βη-equivalence to interaction nets results in several studies of observational equivalence for the interaction combinators. A more systematic way the results are compiled in a paper by Damiano Mazza titled «Observational Equivalence for the Interaction Combinators and Internal Separation» (2006).

31 января 2012, 20:04

love5an

О скобках, ассемблерах из 70х, и всем таком.

Кстати, тут в Juick напомнили про Си, и я решил написать, что я про Си думаю.
Вообще, получилось не только про Си, но, все таки.

Так вот, то что Си еще жив это, если подумать, вершина идиотизма около-ITшных масс.

Сишечка изначально была "языком расширений Unix".
Unix, как я уже как-то писал, был одной из ранних и убогих реализаций Dataflow-парадигмы программирования. Ну, то есть, для более-менее массового железа 70х Unix был не такой уж убогой реализацией, если подумать, но все равно.

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

Так вот, Сишечка использовалась только в Unix, и исключительно только для написания вот этих вот мелких утилит. Ну там, считать файл в stdout, или строчки со stdin отсортировать. Самой сложной программой на сишечке в то время был шелл(даже ядра ранних юниксов были проще шелла). То есть, программы были мелкие, простые, и их было довольно мало(ну можно себе представить железо 70х, учитывая закон Мура и все такое, да?). Всё, на этом предназначение Си заканчивалось. Потому что ну, что еще можно взять с языка, который от ассемблера PDP-11 отличается только тем, что регистры не надо указывать вручную?

Все более-менее ответственные и сложные дела в то время делали на больших и сложных компьютерах, на нормальных языках, типа тех же лиспов(оптимизирующие компиляторы в машкоды для лиспов существовали уже в 70х). Ну, вычисления делали на фортране.

Так вот, на этом всем предназначение и жизнь Си и закончилось бы, если бы IT не наводнили толпы, извините, ебланов, подхвативших первое что попалось под руку и начавших клепать килотонны говнософта. Это сейчас называется "worse is better". Юниксы со временем невероятно жирели и превращались в отвратительных монстров, программы на них тоже жирели, но вот от Си никуда было не деться, потому что Си это сердце, суть, и модель исполнения Юникса. Си и Юникс это одно и то же, как рантаймы лиспов и сами лиспы, например.

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

Особенно мир настольных компьютеров, где уровень железа просто не позволял запускать нормальные языки(кто-нибудь знает на какое железо расчитан DOS? "640 килобайт будет достаточно для каждого", ага. И сравните с лиспами, среднестатистическим реализациям которым уже тогда надо было минимум несколько мегабайт под рантайм и стандартную библиотеку).

Сишечка в 80х стала просто невыносимо популярна. Но уже тогда народ начал понимать ее убогость и ограничения. На волне этого некто Б. Страуструп придумывает препроцессор на Си, теперь известный как самостоятельный язык C++.

Нормальные языки, типа лиспов(а также смоллтолков, и других подобных), в то же время, под конец 80х постигает очень и очень незавидная участь. Дело в том, что в то время лиспы, как одни из наиболее фичастых и продвинутых языков программирования, в основном использовались в сфере разработок в области искусственного интеллекта.
Не только, естественно, в ней(лисп тогда был вообще довольно популярен. Не настолько, насколько Си, но например на tiobe.com можно посмотреть что в 87 году, "Lisp" был на 2м месте рейтинга), но в основном.
Но, так как искусственный интеллект вещь весьма и весьма туманная, никаких особых подвижек в этой области, несмотря на огромные вливания крупных контор, к концу 80х так и не произошло. По этой причине, крупный бизнес и даже, отчасти, академия, от AI начали отворачиваться, и, в конце концов, отвернулись совсем.

При чем тут лисп? Дело в том, что основные вливания в развитие лисповых компиляторов, рантаймов и всего такого, вливания на популяризацию и маркетинг лиспов, шли как раз из сферы AI, и соответственно, как эти вливания прекратились, экосистемы лиспов тоже резко стали хилеть, и в конце концов схлопнулись.
Добавляло "плюшек" к этому всему то, что не только корпорации, а просто даже люди из IT стали отворачиваться не только от сферы AI, но и от всего, что с ней связано, и в частности от лиспов.
Закончилось это всё очень плохо - передовые реализации лиспов(типа CMUCL) просто выбросили на помойку(т.е. в опенсорс), а вендоры коммерческих лисп-систем либо массово обанкротились(Symbolics и другие), либо ушли в подвал, и чтобы хоть как-то выжить, взвинтили цены на свои реализации чуть ли не до облаков(Xanalys(который сейчас LispWorks), Franz, и т.п.). В итоге о лиспах практически все забыли лет на 10, а то и больше.

Сейчас то время(конец 80х, начало 90х) называется "AI Winter".
Вот тут кое-что можно об этом почитать: http://www.flownet.com/gat/jpl-lisp.html

В то же время, как раз тогда, Си и C++ набирают огромную популярность, потом на волне популярности этих языков выходит как-бы-похожая-на-них-но-избавленная-от-основных-недостатков Java(которая, на самом деле, взяла довольно много фич из лиспов, так как разрабатывали ее в том числе многие известные лисперы(Steele и другие)). Во второй половине 90х на волне бума доткомов набирают популярность всякие скриптовые языки, ориентированные на веб(перл, а позже и питон), и вот в итоге мы имеем то что имеем:
1) Повсюду какое-то древнее говно, предназначающееся для мелких расширений юниксов 70х, скриптовые недоделки, сделанные на коленке чтобы быстро-быстро писать какую-нибудь очередную гостевуху, и помеси из этих самых древних говн и скриптовых недоделок с ранними лиспами, отрицающие родство с последними и всячески от него открещивающиеся.
2) Про лисп мало кто что знает, кроме того что там "куча скобок" и "все состоит списков", и вообще, все как минимум его избегают и пугаются, как максимум над его использованием еще и стебутся.
3) В областях CS, связанных с языками программирования, прогресс уперся в какие-то тупиковые темы, типа статических систем типов, и там зациклился(ну это я просто чтобы позлить хаскелистов написал, можете не обращать внимания).

Это всё грустно.

31 января 2012, 19:23

Ъ ООП I/O

Я тут подумал.

Вот как обычно делается обработка IO во всяком там ООП?
Вот у нас есть какой-то скажем TextReader, из него можно читать буквы.
Окей. Но вот нам надо сделать читалку XML-нодов из текста, XmlReader.

Так вот - почему обычно делается так, что TextReader биндится к объекту XmlReader в конструкторе последнего и остается там до окончания его, XmlReader'a, жизни? Т.е. почему вышележащие потоки обычно хранят используемые объекты внутри себя? Да, это может быть, неплохо ложится на C++ное RAII, но если подумать:

Возникают такие проблемы:
  1. Фактически, с такой моделью, мы считаем низкоуровневый поток частью высокоуровневого, и таким образом, узурпируем его функциональность исключительно для реализации работы одного конкретного объекта высокоуровневого потока. Почему узурпируем? Потому что раз один объект - часть другого, то у нас появляется обязанность в деструкторе/финализаторе делать "освобождение" ресурсов первого. Соответственно, ни один другой объект н.у. поток хранить в себе не может, и значит, пользоваться им тоже не может.

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


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



На самом деле, я еще про другие минусы этой модели недавно думал, но сейчас уже не вспомню - написал самые очевидные

А вот что вы думаете о противоположной модели, использующейся, в частности, в контексте "считывателя" Common Lisp - считыватель(штуковина, формирующая лисповые объекты из текста) и текстовые потоки друг с другом не связаны, и "высокоуровневый" поток, при вызове "read" принимает в аргументы низкоуровневый, как бы подключаясь к нему, когда нужно совершить операцию чтения. Это позволяет отделить состояние собственно считывателя(оно хранится в глобальных переменных - *read-base* и пр.) от состояния да и вообще, разновидности, используемого текстового потока, от которого нужен только интерфейс(он должен уметь считывать буквы).

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

Конечно, в контексте программирования - если делать так, как это все делается в контексте лиспового reader'а, то возникает проблема разрастания количества аргументов функций по мере повышения количества слоев обработчиков. Но, тут можно пойти по тому пути, который используется, например, в обработчиках аудио и видео контента, например в виндовом DirectShow - там есть объекты фильтров, которые как-то перегоняют информацию из одной формы в другую. Фильтры создаются заранее, и потом, когда надо, "подключаются" друг к другу(у каждого фильтра есть т.н. разъемы, которые можно друг с другом соединять).

Вобщем, вот так вот. Что думаете?

upd. Вот пример того, о чем я говорю, в терминах интерфейсов на C#: http://pastebin.com/jvcb2F8g
(все-таки не совсем точно описал, что уже заметно по ответам в juick. Да, модель .NET предполагает что весь стек "читалок" стоит на фундаменте Stream, который основан на неуправляемых ресурсах. А я говорю про то, что архитектура должна быть слабосвязной, и "параллельной" (потоки должны связываться входами и выходами, динамически, как в графе фильтров DShow, а не скрывать друг друга или какой-то один "низкоуровневый" поток))

upd2. Короче, вот картинка идеи:

Кстати, на такую модель идеально ложится асинхронность, например.

31 января 2012, 16:41

29 января 2012

Scala@livejournal.com

Подскажите, кто знает про pattern matching

Почитал Pattern Matching in Scala Michael R ̈ egg, mrueegg@hsr.ch December 18, 2009, погуглил (может плохо). В исходники scala лезть не хочеться. Подскажите ленивому где рыть:

scala> Seq((1,2)).map ((a,b) =>)
<console>:1: error: illegal start of simple expression

scala> Seq((1,2)).map {case (a,b) =>}
res7: Seq[Unit] = List(())

{case (a,b) =>} - это что? мы создаем PartialFunction? что там under the hood? Как это работает? Какой документ почитать, описывающий этот механизм, если есть? Или это просто syntatic sugar, тогда как это выглядит без сахара? :)

написал ezhariur 29 января 2012, 07:57

27 января 2012

Codedot

Hard Combinators

Analyzing possible modifications of interaction combinators' representation suitable for uniform memory led to the subject of so-called hard combinators. In particular, trying to compose a universal equivalent of {γ, δ} such as {φ, ψ, ξ} resulted in necessary rotation of ξ with respect to its principal port in some cases. The hard combinators discovered were introduced in a quite recent same-name paper by Bechet and Lippi, namely in 2008. Some idea of Bechet most probably related to the subject was actually mentioned in the paper by Lafont, although without any details and only referencing private communication instead.

Somewhat similar to hard combinators could be of use for uniform memory implementation of interaction systems from the view point of memory-preserving property. Specifically, the total amount of cells involved in a rule for a cut should remain the same before and after reduction. This way, implementation of duplication and annihilation as well as free cells buffer appear to be explicitly embedded into a formal interaction system.

There are also some slides demonstrating the hard combinators. They are available through the following address:

http://iml.univ-mrs.fr/ldp/Seminaire/documents0607/lippi.pdf

Using Lafont's notation, the resulted system can be described as a system of interaction nets with agents of types in {0, 1, –, +}, which has been proved to be Turing-complete with the following interaction rules:

0[0(x, y), x] >< –[–(y)], 1[1(x, y), x] >< –[+(y)];
0[x, 0(y, x)] >< +[–(y)], 1[x, 1(y, x)] >< +[+(y)].

27 января 2012, 15:46

Haskell@Habrahabr

Haskell / Расшифровка кода на языке Haskell (конкурс по ФП в январе 2012)

Заголовок данной статьи является очень двусмысленным. Это станет понятно из дальнейшего изложения. Покамест лишь объявим, что сейчас мы опишем решение задачи конкурса по функциональному программированию, который проводился в январе 2012 года. В качестве задачи почтенным участникам предлагалось расшифровать зашифрованный исходный код простейшим шифром на основе циклического применения ключа к тексту посредством операции XOR. В условиях задачи дополнительно сообщалось, что зашифрован код на языке Haskell, длина ключа не превышает 5 символов, а сам ключ состоит только из заглавных символов латинского алфавита.

В итоге 17 претендентов на призы прислали свои решения, расшифровав исходник, запустив его и прислав правильный код, который этот исходник сгенерировал для участника. Из этих участников 16 получили призы (один прислал правильный ответ после завершения конкурса). Так что теперь остаётся посмотреть, что и как было в этом конкурсе.

написал Darkus 27 января 2012, 09:09

ru_lambda

quick check & test data generator

привет, вопрос такого порядка - у меня есть несколько файлов, из которых построчно надо считать данные и сложить их в списки (скажем 5 списков)

дальше по этим спискам надо нагенерить комбинаций по 5 - выбрать случайный элемент из каждого списка и получить кортеж

курение документации и примеров по quickcheck не позволило ответить на вопрос - как передать в class Arbitrary Cortege списки, прочитанные из файла?

пока варианты - записать списки в IORef, а внутри arbitrary через unsafeIO их читать, но это какой-то аццкий хак получается.

как правильно?

написал The Call of Cthulhu 27 января 2012, 00:50

26 января 2012

Дмитрий Грошев

JSON and Erlang: testing performance

I've tested the most popular JSON encoders/decoders for Erlang, namely Jiffy, JSX and Mochijson2. Tests was done on random data generated by json-generator; particular test cases can be found in "priv" directory in the repository. The results are:

Thanks to logarithmic growth of test size, it's quite hard to reason about linearity of time growth, so I've also added results of linear regression over test data (denoted by dashed line). As one can see, all libraries show near linear growth of time spent on encoding/decoding.

UPD: link on image now leads to png instead of jpg.

26 января 2012, 17:14

David Sorokins WebLog

А не пора ли разбить Scala Library на части?


Библиотека Scala времени исполнения (она же, Scala Library) выросла к версии 2.9.1 аж до восьми с половиной мегабайт с лишним от почти четырех в версии 2.7.7. Для распространения десктопных приложений на Scala это очень плохо. Конечно, есть ProGuard, который убирает все лишнее, но в него нет веры.

Так, только что пропустил свой код через ProGuard и стал тестировать. Ужалось все до мегабайта, но на выходе из приложения получил неприятное NoSuchMethodException. Там создавался анонимный класс, и почему-то один его метод был безжалостно вырезан ProGuard'ом.

Интересно, а собираются ли разбивать Scala Library на части по примеру того, как сделано в .NET? По-моему пора.

написал dsorokin (noreply@blogger.com) 26 января 2012, 09:00

23 января 2012

Codedot

Encoding γ and δ using ξ

The only remaining part of tree representation of minimal interaction systems are the interaction combinators γ and δ themselves. Let us define pseudo-unary agents φ, χ, and ψ. Then, the combinators γ and δ can be encoded as φ(ξ(x, y)) and ψ(ξ(x, y)), respectively. Also, we change the representation of passive connections to υ (free port) and ω (as for permutation) as follows:



Going through agonizing indecision of wondering which exactly model would be the best possible, one might consider a formal interaction system based on {φ, ψ, ξ} which would be equivalent by universality to that of {γ, δ}. (Garbage collection based on ε from the original system of interaction combinators by Lafont can be implemented separately.)

23 января 2012, 11:36

Haskell@Habrahabr

Haskell / Утилита для работы с N-граммами

В одной из своих предыдущих статей («Реализация конструирования N-грамм и генерации псевдо ЕЯ-текста на их основе на языке Haskell») я рекомендовал читателю в качестве самостоятельного упражнения реализовать для построения N-грамм интерактивную среду. В сегодняшней заметке я покажу, как это можно сделать в виде консольного приложения на языке программирования Haskell. За основу мы возьмём эссе № 8 из моей книги «14 занимательных эссе о языке Haskell и функциональном программировании», которое называется «Простой интерпретатор команд», но применим в этой статье немного монадического кун-фу.

Прочитав данную статью, вы узнаете, как при помощи языка Haskell и функционального программирования дать ответы на следующие вопросы:
  1. Как создавать свои собственные монады при помощи техники нанизывания имеющихся монад друг на друга посредством трансформаторов монад?
  2. Как строить цикл интерпретации команд, в котором происходит распознавание введённой команды, её выполнение и вывод результатов (REP)?
  3. Как в этом цикле «таскать» из функции в функцию изменяемое состояние, не прилагая для этого никаких усилий?
  4. Как в любом месте цикла интерпретации бросать исключения и ловить их, обрабатывая красивым образом?
  5. Как предоставлять пользователю возможность вводить команды в очень гибком режиме?
Всё это позволит вам быстро и без особых усилий разрабатывать консольные приложения: серверные приложения, вопрос-ответные системы, интерактивные диалоговые игры и т. д.

написал Darkus 23 января 2012, 04:00

22 января 2012

ru_lambda

case x of

В тренировочных целях пишу парсер простенького бинарного формата и столкнулся с не очевидным, для меня, поведением.
Я побитово читаю данные и перевожу автомат между состояниями: parse :: Word8->ParseState->ParseState
и получении некотрых конкретных значений - я меняю состояние.

Сами эти значения я записал заранее:
data State = S1|S2|S3

b1 = 123
b2 = 213

parse b s = case (b, s) of
        (b1, S1) -> S2
        (_ , S1) -> S3
        (b2, S2) -> S1


При этом оказывается что
(parse 2 S1) = S2
, хотя я хочу что бы сработало второе правило.
причину такого поведения я кажется понял, теперь хотелось бы найти способ столь же красиво и лаконично это записать.

написал Кирилл Кирсанов 22 января 2012, 21:46

Tarantoga

Noir benchmark

Решил немного потестить простое веб-приложение, описанное в предыдущей статье. Тесты проводились на ноутбуке Acer Aspire 5745G (Intel Core i5 2.26Ghz, 4Gb). Обнаружил следующее.

Во-первых, при запуске сайта командой lein run запускаются два java-процесса. Первый, занимающий 28.8Mb -- видимо служебный, т.к. никакие его характерстики за время работы приложения не менялись. Второй, сходу запросивший 76.7Mb, -- само веб-приложение. Я создал два типа сущностей: mypage и mypage2 с CRUD-действиями для них, не используя БД. Т.е. скорость обработки запроса целиком зависила от скорости работы java-машины и не зависела от скорости ввода-вывода или поиска данных в таблицах. При обращении к различным действиям сущности mypage объем потребляемой памяти возрос до 90.3Mb. При обращении к mypage2 -- до 93.5Mb. При повторном обращении к этим же сущностям потребление памяти никак не поменялось. При перезапуске приложения цифры изменились до 96Mb и 99Mb.

Интересно взглянуть на производительность веб-приложения под нагрузкой. Я создал скрипт, делающий 70000 таких запросов:
wget http://localhost:8080/mypage
wget http://localhost:8080/mypage/848867
wget http://localhost:8080/mypage/edit/996283
wget http://localhost:8080/mypage/527997
wget http://localhost:8080/mypage/edit/410389

Сущности с указанными айдишниками, естественно, существовали в приложении. Эти 70тыс. запросов jetty обработал за полчаса, отдавая, таким образом, примерно 2333 динамических страницы в минуту или примерно 39 в секунду.

Самым интересным для меня в этом эксперименте стало потребление памяти и процессорного времени.

Сначала jetty затребовал 92% процессорного времени, но его запросы довольно быстро сократились до 40%. Затем, довольно медленно, и они стали снижаться. К концу эксперимента jetty потреблял всего 4% процессорного времени.

Аналогично, запросы памяти сначала были немалыми. Довольно быстро jetty "откушал" 232.8Mb, после чего потребляемая память стала уверенно снижаться, остановившись на отметке 84.6Mb.

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

Такое снижение потребляемой памяти и процессора стало возможно благодаря run-time оптимизациям java-машины (в Sun некогда здорово над этим поработали). Вот и скажи теперь, что java -- медленная...

написал Dmitry (noreply@blogger.com) 22 января 2012, 18:01

deniok

Про свободные теоремы

Свободная теорема - это содержательное тождество, которое следует из типа любой параметрически полиморфной функции в полиморфно типизированном лямбда-исчислении. В "простых" полиморфных системах (вроде System F) свободные теоремы выполняются безусловно. В более богатых системах, например, в Хаскелле, имеются некоторые ограничения, возникающие из-за наличия в языке примитивов неограниченной рекурсии fix и строго вычисления seq, подробности см. [Johann06].


Свободная теорема для любой полиморфной функции с типом
t :: [a] -> [a]
имеет вид
map g (t xs) == t (map g xs)
где g - совершенно произвольная функция, а xs - список, передаваемый в t в качестве аргумента. Например, взяв в качестве t функцию tail, в качестве g функцию succ, а в качестве xs список [1,2,3,4], получим
map succ (tail [1,2,3,4]) 
== map succ [2,3,4]
== [3,4,5]

tail (map succ [1,2,3,4])
== tail [2,3,4,5]
== [3,4,5]

Свободная теорема для
t :: [a] -> [a]
может быть получена из коммутативности квадрата
          t
     [a] --> [a]                a
      |       |                 | 
 map g|       |map g            |g
      v       v                 v
     [b] --> [b]                b
          t
Здесь g :: a -> b, а t, как параметрически полиморфная функция, может действовать на список любого типа: хоть [a], хоть [b]. Коммутативность таких квадратов есть следствие теоремы параметричности [Reynolds83], [Wadler89], [Johann06]. В терминах теории категорий полиморфная функция t - это естественное преобразование функтора списка в функтор списка.

Можно записать и более общую версию этой теоремы, годную для произвольных функторов. Возьмём
t :: Functor fl, Functor fr => fl a -> fr a
тогда квадрат
            t
      fl a --> fr a                a
       |         |                 | 
fmapl g|         |fmapr g          |g
       v         v                 v
      fl b --> fr b                b
            t
даст свободную теорему
fmapr g (t x) == t (fmapl g x)
(Хотя в Хаскелле можно было бы использовать универсальное имя fmap для класса типов Functor, удобнее различать поведение разных функторов - отсюда обозначения fmapl и fmapr.)

В качестве примера можно привести функцию maybeToList. Она имеет тип Maybe a -> [a], что даёт свободную теорему
map g (maybeToList mv) == maybeToList (fmap g mv)
Взяв в качестве g функцию succ, а в качестве mv величину, например, Just 41, получим ожидаемый результат и в левой и в правой частях равенства (проверьте это).

Свободные теоремы полезно записывать в бесточечном стиле - это позволяет глубже проникнуть в их естество:) Первый шаг на этом пути для случая с двумя произвольными функторами очевиден - избавиться от экземпляра x функтора fl a. Это даст
fmapr g . t == t . fmapl g
Следующий шаг - избавление от t. Для этого воспользуемся сечениями композиции
fmapr g . t 
==(fmapr g .) t

t . fmapl g 
== (. fmapl g) t
Имеем свободную теорему для типа fl a -> fr a в виде сечений
(fmapr g .) == (. fmapl g)
(В качестве упражнения попробуйте избавиться и от g.)

Определим наиболее общие типы левой и правой частей последней версии нашей свободной теоремы. В стандартном контексте g :: a -> b имеем
(fmapr g .) :: Functor fr => (d -> fr a) -> d -> fr b
(. fmapl g) :: Functor fl => (fl b -> e) -> fl a -> e
Наивная унификация даст наиболее общий из возможных типов первого ранга
(Functor fl, Functor fr) => (fl b -> fr b) -> fl b -> fr b
К сожалению, это не то, что нам нужно: тип g станет окажется менее общим, чем хотелось бы, а именно b -> b. Однако, переходя к типам второго ранга, легко написать нужный нам тип, подходящий для обоих выражений
(fmapr g .) :: (Functor fl, Functor fr) => 
  (forall c . fl c -> fr c) -> fl a -> fr b
(. fmapl g) :: (Functor fl, Functor fr) => 
  (forall c . fl c -> fr c) -> fl a -> fr b


Несколько упражнений.

Проверьте, что для указанных типов имеют место приведённые свободные теоремы. Выведите "грамотно унифицированный" тип обеих частей равенства.
[[a]] -> [a]
(map g .)  ==  ( . map (map g))

(Functor flo, Functor fli, Functor fr) =>
  flo (fli a) -> fr a
(fmapr g .)  ==  ( . fmaplo (fmapli g))

[a] -> [a] -> [a]
((map g .) .)  ==  ((. map g) .) . (. map g)

(Functor fl, Functor fc, Functor fr) =>  fl a -> fc a -> fr a
((fmapr g .) .)  ==  ((. fmapc g) .) . (. fmapl g)
Здесь g :: a -> b - совершенно произвольная функция, а fmapXXX - это fmap для функтора fXXX.



PS. Ну и чтобы всё было в одном месте - автоматический генератор свободных теорем.

22 января 2012, 16:54

21 января 2012

Codedot

Interaction Combinators in Uniform Memory Compactly

Using pseudo-agents of type τ which allows arbitrary amount of auxiliary ports and type ξ which allows arbitrary amount of connections on primary port, let us also introduce the definitions of ο, ρ, and ζ, then provide representation of an arbitrary interaction combinator net N into a form suitable for uniform memory whose nodes are equivalent to pseudo-agents' type ξ:



The interaction rules for this system are yet to be done. Since the above representation cannot have any cuts, any reduction rules will produce a graph rewriting system which is formally not an interaction system. The reduction rules will involve two trees τ, the root node ο, and the next redex ρ. Since ο includes a virtually infinite list of free ξ pseudo-agents, the reduction rules can be defined a way preserving the amount of ξ pseudo-agents involved.

21 января 2012, 17:58

«Interaction combinators» как бинарные деревья

Продолжим преобразовывать «interaction nets» на основе «interaction combinators» с целью последующей реализации системы в однородной памяти. Для этого введем обозначение: тип ξ псевдоагентов, главный порт которых может быть связан одновременно с произвольным количеством дополнительных портов агентов γ и δ и псевдоагентов ξ. Псевдоагенты ξ напрямую соответствуют ячейкам однородной памяти. Один псевдоагент типа ξ в системе мы будем обозначать ν — он будет выступать в роли выделенной ячейки NULL однородной памяти. Графически позволим себе изображать эту выделенную ячейку произвольное количество раз. Деревья мы будем обозначать как псевдоагенты типа τ, имеющие произвольное количество дополнительных портов.

Заметим также, что любая «interaction net» с γ и δ может быть записана в виде набора бинарных деревьев с явными связями между корнями и листьями. При этом соединения между корнями мы будем называть активными, так как они представляют собой редексы, а между листьями — пассивными. Рассмотрим представления тех и других отдельно.

Начнем с активных соединений τ1 >< τ2. Они могут быть представлены как деревья ξ(τ1, τ2). Список редексов, построенный ранее, в свою очередь, запишем как ν(ξ(τ1, τ2), ξ(ξ(τ3, τ4), … )), оставив пока в стороне буфер свободных ячеек. (На самом деле, буфер свободных ячеек можно заменить искусственными аннигиляционными редексами.)

Теперь обратимся к пассивным соединениям. В отличие от редексов, где достаточно деревьев (хранятся лишь связи от главного порта к дополнительным, но не наоборот), там необходимы двусторонние равноправные ссылки. Но эта конструкция уже была построена для двусвязных ненаправленных списков, и применив ее к τ1(x), τ2(x), получим τ111, ξ2)), τ222, ξ1)). (Связываемыми объектами здесь выступают сами ξ1 и ξ2. Этой информации достаточно в общем случае, так как на месте пассивных связей не порождаются новые редексы, а лишь могут продолжаться деревья на месте листьев — этого легко добиться, заменив ξ на корневой агент соответствующего дерева.)

Наконец, свободные порты поставим в соответствие связям с главным портом ν. Активным это соединение будет вести себя как правило ε >< α(ε, ε) в оригинальной системе «Interaction Combinators» (Lafont), но без дублирования ν, учитывая множественные связи на его главном порту.

Прежде чем перейти к иллюстрации результатов, заметим, что благодаря выбранным конструкциям с исключительным псевдоагентом ν, активные и пассивные связи однозначно идентифицируются даже в однородной памяти. В графическом представлении система выглядит следующим образом:

21 января 2012, 11:06

Список редексов для «interaction combinators»

Для выполнения условия работы в реальном времени требуется не только конечные наборы типов агентов и правил для них, но также явные дублирование и аннигиляция агентов с помощью буфера свободных ячеек и время O(1) на выбор следующего редекса с помощью дополнительных связей. Этих свойств можно добиться преобразованием произвольной «interaction net» N на основе «interaction combinators», имеющую n редексов α2i - 1 >< α2i, где i = 1, … , n, следующим образом:



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

[Прим. автора сплошных глубоких постов®: уже боевые домики набигают, чтобы можно было грабить корованы.]

21 января 2012, 11:05

Russian-speaking Scala Enthusiasts

Предварительный список докладов на scalaby#8

На scalaby#8 нас будут ожидать следующие доклады: Зарегистрироваться на встречу можно отметившись на странице в Facebook, написав мне письмо, или оставив комментарий к этому посту.

написал scala-enthusiasts-belarus@googlegroups.com (Scala Enthusiasts Belarus) 21 января 2012, 08:00

20 января 2012

Clojure on Android

Прошляпил Clojure Conj

Оказывается по Clojure была конференция Clojure Conj, аж на целых 3 дня. Проходила она в ноябре 2011 года. Видео доступно здесь.

Что самое приятное, так это то что есть люди заинтересованные в запуске и использовании clojure на Android. На clojure-conj был доклад на эту тему. Правда видео я не нашел. Если у кого есть напишите в комментариях.
Здесь ссылки на презентации докладчиков.

написал Алексей (noreply@blogger.com) 20 января 2012, 12:41

19 января 2012

Lisp@Habrahabr

Персональные блоги / «Жизнь» Конвея из каждой буквы, только в Emacs!

Одним рабочим декабрьским вечером



Все мы так или иначе сталкивались с игрой «Жизнь» Конвея. Кто-то писал сам, кто-то смотрел и дивился, кто-то играл…

Под новый год, сидя с коллегой за одним компом и решая какую-то проблемку в конце рабочего дня, мы немного отошли от проблемы и как-то дело свелось к M-x life (мы оба пользуемся Emacs), &emdash кто не знаком с Emacs — это команда запуска игрушки «Жизнь» в Emacs.

Стандартное неинтересно


Вроде бы о чем тут говорить. Да вот только реализация в Emacs имеет около 10 «вшитых» начальных позиции, которые мягко говоря неинтересны.

Вы знаете, что:
  • Конвей первоначально предположил, что никакая начальная комбинация не может привести к неограниченному размножению и предложил премию в 50 долларов тому, кто докажет или опровергнет эту гипотезу.
    цитата из Wikipedia;
  • К настоящему времени более-менее сложилась следующая классификация фигур:
    цитата из Wikipedia;
  • Emacs — это расширяемый текстовый редактор.


написал aleksandrvin 19 января 2012, 14:00

18 января 2012

ru_lambda

[F#] Скрестить комбинаторы с FsYacc

Имеется парсер на комбинаторах. Предположим что восстановить грамматику из комбинаторов мы можем. Хочется (автоматически) заменить код на комбинаторах на вызов FsYacc.

Проблема: Парсер комбинаторы парсят потоки Char а FsYacc хочет полноценный лексер (lexbuf и функцию из lexbuf в токены)
Возможное решение: навелосипедить свою библитеку комбинаторов, где она будет принимать что-то лексероподобное, но посложнее чем просто поток символов.
Ещё возможное решение: Оставить поток сиволов как есть, а некоторые парсер функции считать кусками лексера и превращать в регулярки, например. Т.е. из комбинаторов генерить свой лексер.

Лично мне первое какжется по-проще.

А вы бы как сделали?

P.S. Чуть по-конкретнее, ибо меня уже завалили вопросами.
На входе: дотнетовский класс, где методы --- это парсер-функции (т.е. имеют тип _ -> result)
На выходе: дотнетовский класс, где вызовы некоторых методов подменены на вызовы fsyacc (Но типы те же и снаружи кажется, что ничего не поменялось)

Здесь "некоторые методы" это методы, которые программист захотел сделать на fslex.
Основные грабли: fsyacc ходит парой с fslex. А комбинаторы обычно парсят сразу поток сиволов (без токенизации, т.е. им лексер не нужен)
Т.е. надо или как-то навелосипедить свои комбинаторы которые принимают не поток символов, а что-то лексеро-подобное. Либо из кода комбинаторов получать тем или иным образом информацию о лексере и генерить кроме кода на fsyacc код для fslex (что-то звучит страшновато)

Надеюсь что-то прояснилось.

написал dimitrykakadu 18 января 2012, 15:38

17 января 2012

Codedot

Нотация Lafont для правил «interaction nets»

Одним из наиболее компактных способов описания правил «interaction nets» является эзотерический язык программирования, предложенный в статье 1989 года «Interaction nets» (Lafont) — см. стр. 104. Он основан на двух наблюдениях. Во-первых, левая часть любого правила всегда содержит лишь два агента, которые связаны своими главными портами. Во-вторых, правая часть каждого правила никогда не содержит редексов, из чего следует, что дополнительные порты обоих взаимодействующих агентов в общем случае являются корнями деревьев, листья которых связаны между собой произвольным образом. Поэтому описанию подлежат лишь сами деревья, а также связи между листьями.

В качестве примера возьмем первое правило для системы с агентами типов γ и δ. Сначала оттолкнемся от более простой избыточной нотации, не требующей дополнительных пояснений, в которой первый аргумент агента соответствует главному порту:

γ(x, y, z), δ(x, v, w) → δ(y, a, b), δ(z, c, d), γ(v, a, c), γ(w, b, d).

Воспользуемся первым наблюдением:

γ[y, z] >< δ[v, w] → δ(y, a, b), δ(z, c, d), γ(v, a, c), γ(w, b, d).

Теперь заметим, что каждый главный порт в правой части использует один из дополнительных портов в левой. Значит, мы можем без потери информации записать агенты правой части на местах соответствующих дополнительных портов левой:

γ[δ(a, b), δ(c, d)] >< δ[γ(a, c), γ(b, d)].

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

δ[x, y] >< δ[x, y], γ[x, y] >< γ[y, x].

17 января 2012, 20:25

Библиография по «interaction combinators»

Решил подвести промежуточные итоги, сначала реконструировав свой небольшой поиск, который привел к «interaction combinators», а затем представив список статей по данной теме.

Постановка задачи была такой, что требовалось реализовать экстенсиональную версию оптимальной редукции для Тьюринг-полного чистого бестипового λ-исчисления на основе однородной памяти с работой в реальном времени и без потери памяти при NC = 2. Я начал решать эту задачу, познакомившись с монографией по оптимальной редукции. Так как оригинальный алгоритм Лэмпинга, как и основные его варианты, является «interaction system» с бесконечными (из-за наличия индексов) наборами типов агентов и правил взаимодействия, требование работы в реальном времени выполняться не могло. Поэтому я сосредоточился на единственном варианте оптимальной редукции с конечным набором операторов, который был представлен в монографии, — это нотация с помощью шин.

Далее, я заметил, что в некоторых случаях поведение разделяющей ноды может симулировать работу как брекетов и круассанов, так и самой ширины шины разделяющей ноды. Это подтолкнуло меня к попытке построить систему с поляризованным представлением λ-выражений и редукциями γ, β и η. Различные варианты этой системы оказались некорректными, однако, их изучение дало необходимые ключевые слова, чтобы продолжить поиск.

Одной из «interaction systems» с конечными наборами типов агентов и правил взаимодействия оказался самый эффективный, по крайней мере, на 2004 год, вариант KCLE [1]. Тем не менее, огромное количество типов и правил данной системы затрудняло ее реализацию в однородной памяти. Заметив в [2, 3, 4] использование, среди прочих, агентов γ и δ с правилами взаимодействия γ[δ(a, b), δ(c, d)] >< δ[γ(a, c), γ(b, d)], δ[x, y] >< δ[x, y] и γ[x, y] >< γ[y, x], я стал копать в сторону их происхождения. Оказалось, что как нотация для правил выше, так и сама система {γ, δ} принадлежат Yves Lafont. Называемая «interaction combinators», она была введена и подробно рассмотрена в одноименной статье [5]. В частности, доказывается, что система {γ, δ} полна по Тьюрингу.

Таким образом, оставалось лишь изучить способы представления λ-выражений с помощью исключительно «interaction combinators» [6], а также дополнительных правил преобразования за формальными пределами «interaction nets», которые бы заменяли η-редукцию в λ-исчислении [7]. То есть на данный момент вопрос остается лишь в выборе представления «interaction combinators» с дополнительными η-правилами в однородной памяти. После этого можно продолжить работу над проектом MLC (Macro Lambda Calculus), основной целью которого является отделение операций над данными от «control flow» постановкой барьера ввода-вывода по старой схеме Haskell через прерывания по обращению к памяти.

1. Ian Mackie. Efficient λ-Evaluation with Interaction Nets (2004).
2. Ian Mackie. Static analysis of interaction nets for distributed implementations (1997).
3. François-Régis Sinot. Token-Passing Nets: Call-by-Need for Free (2005).
4. Sylvain Lippi. Encoding left reduction in the λ-calculus with interaction nets (2002).
5. Yves Lafont. Interaction Combinators (1997).
6. Ian Mackie, Jorge Sousa Pinto. Compiling the λ-calculus into Interaction Combinators (1998).
7. Maribel Fernandez, Ian Mackie. A Theory of Operational Equivalence for Interaction Nets (2000).

17 января 2012, 20:24

darkus

Результаты январского конкурса по функциональному программированию

А сегодня мы подведём итоги январскому конкурсу по функциональному программированию, который прошёл в пятницу 13-го. Я немного налажал, поэтому конкурс пришлось перезапустить, но на результатах это не сказалось. После этого сама задача мне казалась уже неинтересной, однако у моих читателей она вызвала интерес, и в итоге в конкурсе участвовало 16 человек, и один человек опубликовал код после завершения конкурса. А вот и результаты:

Место ID Порядок Код Призы
I Alexey Kishkin@G+ 1 4W55M74 Книга с автографом
Скидка 2 %
I [info]oktagen_student 2 2G68L88 Книга с автографом
Скидка 4 %
II [info]jtraub 3 6Y21X16 Журнальчик с автографом
Скидка 2 %
III Константин Лихоманов@VK 4 3R67K68 Персональный электронный экземпляр
Скидка 4 %
IV [info]aaknop 5 4E90Z19 Персональный электронный экземпляр
Скидка 4 %
V [info]rastafarra 6 0D72D56 Персональный электронный экземпляр
Скидка 2 %
VI Игнат Толчанов@G+ 7 2R11E20 Персональный электронный экземпляр
Скидка 2 %
VII [info]bakhmatov 8 2T00E52 Персональный электронный экземпляр
Скидка 2 %
VIII afiskon@twitter 9 2L18E02 Скидка 8 %
IX [info]dimonster_1983 10 1D26I95 Персональный электронный экземпляр
Скидка 2 %
X [info]pankdm 11 4K46T66 Персональный электронный экземпляр
Скидка 2 %
XI [info]dbg 12 3U40L37 Персональный электронный экземпляр
Скидка 2 %
XII [info]lelf 13 1G00G13 Персональный электронный экземпляр
Скидка 4 %
XIII [info]ushastyi 14 3M23N73 Персональный электронный экземпляр
Скидка 2 %
XIV Anton Dubovik@G+ 15 2G40O39 Персональный электронный экземпляр
Скидка 4 %
XV [info]stdray 16 1G33H66 Персональный электронный экземпляр
Скидка 4 %
[info]dmitry_vk 17 8S86X13

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

Всех, кто перечислен в таблице, прошу как можно раньше связаться со мной по почте (darkus.14@gmail.com) и предоставить свои полные Ф. И. О. для оформления автографов. Тех же, кому положены материальные призы, также прошу сообщить метод передачи оных — при помощи личной встречи или по почте. В случае второго варианта почтовые расходы остаются за вами.

Апелляции принимаются в комментариях к этой записи.

Предыдущие конкурсы:
1. Задача о возрастах детей математика: условие, результаты.
2. Кто на ком женат: условие, результаты.
3. Задачи о переправах: условие, результаты.
4. Задача о надёжности кода: условие, результаты.
5. Задача о конструировании чисел из чисел: условие, результаты.

Если вы хотите дополнительно отблагодарить организатора (то есть меня), но не знаете как, то вам сюда. Собранные благодарности пойдут прямиком в ФП(ФП).

17 января 2012, 13:45

Условия январского конкурса по функциональному программированию

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

Теперь же перейдём к условиям январского конкурса. Во времена, когда я был студентом и учился в институте, я мощно рубал информатику и программирование, и слава обо мне шла не только в группе или потоке, но и вообще по всему курсу. Так что ко мне приходили двоечники со всего института. Я им, бывало, помогал решать лабораторные работы по информатике. А иной раз подшучивал по-программистски. Написал кодировщик, который циклически и побайтно применял к заданному файлу ключевое слово при помощи операции XOR (исключающее ИЛИ). Из файла получалась полная белиберда, но если применить то же самое слово тем же способом, то файл полностью восстанавливался (в силу закона логики (a XOR x) XOR x = a). И вот как-то раз я упомянул про это в разговоре с одним своим знакомым. А он сказал, что для него никакого труда не составит расшифровать закодированный таким образом файл, ибо это не шифрование, а полное фуфло. Поскольку тот учился на факультете информационной безопасности, я было поверил, а потом решил проверить. Итак, мы договорились, что я дам ему зашифрованный файл с исходниками на языке C++ размером не менее 10 Кб. Размер ключа был неограничен, но он должен был состоять только из символов латинского алфавита в заглавном регистре. Как сейчас помню, ключом было слово EXCALIBUR. Я выдал знакомому файл и договорились, что не позже, чем через три дня, он отдаст мне результат. В итоге, я проиграл — он взломал код, причём сделал это достаточно быстро. Не знаю, чем он пользовался для работы, история об этом уже умалчивает.

Это была прелюдия. А теперь условия. Вот здесь можно скачать файл для расшифровки: зашифрованный файл. Файл заархивирован, так что сначала его надо разархивировать. После этого придумать алгоритм и расшифровать файл. После расшифровки вы получите код на языке Haskell. Его надо запустить один раз. Функция main выдаст на экран семизначный код, который надо опубликовать в комментариях к этой записи вместе со ссылкой на свой исходный код, который помог вам решить задачу. Тем, кто сомневается, как запустить код на языке Haskell, рекомендую воспользоваться сервисом Codepad. Ограничения на ключ таковы: ключ состоит не более, чем из пяти заглавных букв латинского алфавита. Можете расшифровать силой, можете найти какие-либо специальные эвристики. Дерзайте.

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

Вроде бы всё. Условия по получению призов традиционные. И как обычно призываю вас разнести весть о конкурсе по всем этим вашим интернетам. Пусть больше хороших участников участвует. Призов хватит на всех.

Дополнение 1: Поскольку я как-то не предугадал «грязного хака» с подстрокой module Main в начале файла, ссылка на файл обновлена. Прошу скачать новый файл, все присланные результаты обнулены с 09:30 13.01.2012.

Дополнение 2: 16.01.2012 в 07:22 комментарии раскрыты, начато подведение итогов.

Дополнение 3: Результаты опубликованы.

Предыдущие конкурсы:
1. Задача о возрастах детей математика: условие, результаты.
2. Кто на ком женат: условие, результаты.
3. Задачи о переправах: условие, результаты.
4. Задача о надёжности кода: условие, результаты.
5. Задача о конструировании чисел из чисел: условие, результаты.

Если вы хотите дополнительно отблагодарить организатора (то есть меня), но не знаете как, то вам сюда. Собранные благодарности пойдут прямиком в ФП(ФП).

17 января 2012, 13:45

ru_lambda

Вопросы новичка.

Продолжаю освоение хаскеля.

Вот функция, покомпонентно преобразующая rgb цвет из Double в Word8
type ColorF = (Double, Double, Double)
type ColorB = (Word8, Word8, Word8)

f2b:: ColorF->ColorB
f2b (r,g,b) = (p r, p g, p b)
               where p = round . (*255)
               where p x = round $ x*255



1) Какой из вариантов where предпочтительнее?
2) Мне не нравится запись (p r, p g, p b), так и хочется сделать аналог map для коржей. Это правильное желание?
3) Я правильно понимаю, что если хочу производительности на изображениях ([ColorB])то мне нужны ByteString и придется отказаться от удобных типов в угоду производительности?

написал Кирилл Кирсанов 17 января 2012, 13:28

Haskell - State ( IO ) или IO ( State )?

 Разбираюсь тут в монаде State. Возник вопрос - а как бы ее поудобнее использовать вместе с монадой IO? Например, если я хочу не только состояние, но и использовать System.Random.

Вот что мне удалось набросать самому:
а) Используем только монаду State, при этом храним в состоянии генератор типа StdGen
б) Монада IO,  вложенная в монаду State
в) Монада State, вложенная в монаду IO

В общем-то, все эти способы кажутся рабочими, но от них просто разит топорностью.

Вопрос - как "правильно" такие вещи делаются в Haskell?

Ну и пользуясь случаем - может кто-нибудь показать пример работы с type IntState a = State Int a, то бишь без структур? Не могу понять, как в этом случае считать/записать состояние. С этим я разобрался благодаря [info]aaknop

UPD: Всем спасибо за помощь, вроде во всем разобрался.

написал Алексеев Александр 17 января 2012, 04:37

16 января 2012

David Sorokins WebLog

Глубоко-вложенные вычисления


Тема возникла из моих ответов на вопросы, заданные другим человеком на форумах. Речь пойдет о рекурсивных вычислениях, когда глубина вложенности выше предельно допустимой для стека вызовов. Я покажу, как такая задача может быть решена на Common Lisp. Сразу отмечу, что это возможно не всегда, но в большинстве случаев работает.

В основе лежит та же самая идея, что используется в F# Async. Мы просто переводим наши рекурсивные функции на язык продолжений.  Пугаться здесь не стоит, сами мы этого делать не станем. За нас все самое сложное и рутинное сделают WITH-CALL/CC и его специальная версия для функций DEFUN/CC из пакета CL-CONT. Ниже везде предполагается, что пакет CL-CONT импортирован.

Но прежде определимся с исходной задачей. Ниже приведены функции, которые вылетают со Stack Overflow.

(defun compute (x)
  (cond ((zerop x) 0)
        (t (+ (compute (1- x))
              1))))


(defun run ()
  (compute 10000000)) ;; Stack Overflow

Функции намерено взяты такими. Сами по себе эти функции ничего не представляют. Нам интересно то, что функция COMPUTE рекурсивная, с большой глубиной, вызов - не хвостовой. В общем случае, рекурсивных вызовов может быть несколько.

Перепишем функции через продолжения. Функцию COMPUTE определим через DEFUN/CC. Т.е. она будет возвращать не обычное значение, а вычисление, которое еще нужно запустить. В F# это примерно означало бы то, что функция COMPUTE возвращала бы некоторое значение типа Async<'a>.

(defun/cc compute/cc (x)
  (cond ((zerop x) 0)
        (t (+ (compute/cc (1- x))
              1))))

Таким образом мы должны преобразовать всякую функцию, вложенность которой может быть огромной. Вызывать их можно из DEFUN/CC и WITH-CALL/CC:

(defun run/cc ()
  (with-call/cc (compute/cc 10000000)))

Если у вас SBCL, то на этом можно остановиться. У этой лисп-машины превосходный оптимизатор хвостового вызова. У нас фактически все операции COMPUTE превращаются в цепочку хвостовых вызовов. Поэтому все работает. Стек как бы уходит в память.

Увы, не все лисп-машины хорошо оптимизируют хвостовые вызовы. Для CLozure CL и LispWorks Personal мы по-прежнему получим Stack Overflow. К счастью есть выход - использовать трамплин.

Внутри вычисления DEFUN/CC нам доступно продолжение. Если глубина стека вызовов стала большой, то мы можем запомнить продолжение и раскрутить в обратную сторону стек, возвращая управление некоторому внешнему циклу. Внутри этого цикла мы будем проверять, а нет ли у нас очередного продолжения для, так сказать, продления вычисления. Если есть, то запускаем это продолжение. Фокус состоит в том, что при запуске продолжения стек вызовов уже очищен, что нам и требуется.

Сначала определим утилиты трамплина:

(defparameter *cont* nil)


(defun/cc trampoline-push/cc ()
  (call/cc 
   (lambda (k)
     (push k *cont*))))


(defmacro trampoline/cc (expr)
  (let ((result (gensym)))
    `(progn
       (trampoline-push/cc)
       (let ((,result ,expr))
         (trampoline-push/cc)
         ,result))))


(defmacro with-trampoline/cc (&body body)
  (let ((result (gensym)))
    `(let ((,result nil))
       (with-call/cc
         (trampoline-push/cc)
         ,@body)
       (loop while *cont*
          do (let ((cont (pop *cont*)))
               (setf ,result (funcall cont))))
       ,result)))

Утилита TRAMPOLINE-PUSH/CC кладет продолжение вычисления в ячейку *CONT* и возвращает управление внешнему циклу из WITH-TRAMPOLINE/CC, откуда все должно быть запущено. Макрос TRAMPOLINE/СС оборачивает заданное выражение, где трамплин вызывается до и после вычисления выражения.

Мы можем использовать трамплин часто, но это неэффективно. Пусть он вызывается на каждой сотой итерации:

(defun/cc smart-compute/cc (x)
  (cond ((zerop x) 0)
        ((zerop (mod x 100))  
         ;; on every 100th iteration use the trampoline
         (+ (trampoline/cc (smart-compute/cc (1- x)))
            1))
        (t 
         (+ (smart-compute/cc (1- x))
            1))))


 ;; No Stack Overflow
 (defun smart-run/cc ()
  (with-trampoline/cc (smart-compute/cc 10000000)))

Это работает даже для CLISP, где нет никакой оптимизации хвостового вызова. Мы успешно имитирует рекурсивный вызов с глубиной вложенности десять миллионов!

написал dsorokin (noreply@blogger.com) 16 января 2012, 19:47

Теория категорий

вычислительная теория категорий

Что уважаемое сообщество может порекомендовать из литературы по вычислительной теории категорий? Замечательную книгу Rydeheard, Burstall "Computational Category Theory" усвоил, однако её можно считать скорей введением и обзором возможностей ML в смысле реализации категориальных определений, чем существенной почвой для работы. Собственно, два сколько-нибудь существенных приложения в ней - алгоритм унификации и описание семантики, служат как proof of concept потенциальных расчётных задач: они неэффективны и могут быть лучше реализованы без привлечения высокоуровневых категориальных абстракций.

Какие интересные задачи решают, опираясь на (существенно) конструктивную природу категорий? Возможно, что-то из теории топосов? Заранее спасибо за любые предложения!

написал Just the One of the Few 16 января 2012, 16:29

Lisp@Habrahabr

Искусственный интеллект / [Из песочницы] Neural network in scheme

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

написал incogn1too 16 января 2012, 11:14

Eugene Burmako

Выступление на встрече #scalaua

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

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

Я затранскрибировал самые важные моменты демонстрации и выложил их в виде слайдов: http://scalamacros.org/talks/2012-01-14-RuAlphaKepler.pdf - получился неплохой getting started гайд по макросам (жалко, что не на английском языке - переводить пока нет времени).

Замечательное чувство, когда тебя не только понимают, но еще и высказывают идеи, о которых сам даже и задумывался! Большое спасибо, ребята - тема с лифтингом переменных из лексического скоупа очень-очень интересна, завтра вброшу ее Мартину. Отдельные благодарности Ярославу и Руслану ([info]rssh) за организацию :)

16 января 2012, 11:01

Codedot

Полнота «interaction nets» с агентами типов γ и δ

Очевидно, что существуют системы «interaction nets», которые можно сократить, уменьшив как набор правил, так и набор типов агентов. Если предположить, что есть системы, которые упростить нельзя, интересны критерии возможности редукции систем. Вот что удалось найти по этому вопросу.

В статье «Static Analysis of Interaction Nets for Distributed Implementations» (Ian Mackie) агенты типов γ и δ сравниваются с комбинаторами K и S в смысле полноты систем, построенных на их основе. (Более точно, в статье рассматривается набор γ, δ, ε, однако, ε можно убрать из рассмотрения, так как для реализации «сборки мусора» этого агента недостаточно, а тогда, когда ε должен отработать до конца, «сборщик мусора» все равно должен выполнить его работу.) Напомним правила взаимодействия γ и δ:



Известно, что любой λ-терм можно записать с помощью K и S следующим преобразованием:

λx.x = S K K;
λx.M = K M, x ∉ FV(M);
λx.M N = S (λx.M) (λx.N).

Именно поэтому полноту произвольного варианта λ-исчисления обычно показывают предъявлением K и S в его терминах. Примем во внимание, что существует также комбинаторная логика с одноточечным базисом {X}, например такая, что K = X X и S = X X X. Тем не менее, никакой X не может иметь нормальной формы, причем для X нельзя записать правило редукции, как в случае K x y = x и S x y z = x z (y z). Таким образом, одноточечная комбинаторная логика полна лишь с точки зрения представления, но не редукции.

Полнота систем «interaction nets» по определению не может быть ограничена представлением, она обязана также вести к вычислительным способностям. Это приводит нас к вопросу о том, может ли быть полный вариант системы «interaction nets» с единственным типов агентов ξ и, как следствие, единственным правилом ξ[…] >< ξ[…], и если нет, то почему.

Update: Первоисточником γ и δ является статья 1997 года «Interaction Combinators» (Lafont). Собственно агенты этих типов и называются комбинаторами. В этой статье явно показывается, почему именно они универсальны. Через год после нее был представлен способ представления λ-выражений исключительно с помощью комбинаторов γ и δ: работа «Compiling the λ-calculus into Interaction Combinators» (Ian Mackie и Jorge Sousa Pinto) 1998 года.

16 января 2012, 07:42

ru_declarative

асинхронные изменения

Здравствуйте.

У меня возник вопрос по Haskell. Допустим, я хочу разработать многопользовательскую игру, где игроки асинхронно вносят изменения в мир. Меня интересует, где в таком случае хранится мир, ведь он может существовать только внутри некоторого вычисления, а в чисто функциональных языках, вроде как вычисления изолированны-друг-от-друга, следовательно игроки не смогут этот мир менять. Как разобраться с этой проблемой? Буду благодарен, если будет указанно направление, куда копать.

написал ankalagonblack 16 января 2012, 06:48

15 января 2012

Codedot

Сравнение производительности реализаций ЯФП

Почему бы не инициировать срач на тему «Haskell в отстой», вместо того чтобы писать об абстракциях вроде нотации с помощью шин или «interaction combinators»?

Наверное, мало кто открыл упоминавшуюся ранее статью 2004 года «Efficient Lambda Evaluation with Interaction Nets» (Ian Mackie). В ней, в частности, приводится сравнение производительности на нумералах Черча машины KCLE, построенной на основе «interaction nets», против BOHM, сделанной на основе алгоритма Лэмпинга. Для справки, она в десятки раз быстрее на выражениях чуть сложнее 2 * 2.

Полагаю, еще меньше людей взглянули на собственно монографию по оптимальной редукции, где, помимо прочего, сравнивается производительность оптимальной BOHM с Haskell. Она тоже, в свою очередь, в десятки раз быстрее Haskell на выражениях чуть сложнее 3!.

Таким образом прирост, по крайней мере, в 1000 раз. Ну, поехали!

Disclaimer: Использовался Yale Haskell Y2.3b-v2.

15 января 2012, 19:23

Пакет TikZ для «interaction nets»

Обнаружил в англоязычной Википедии статью об «interaction nets», с помощью которых описана упомянутая ранее реализация оптимальной редукции. Оказывается, существует соответствующий пакет для TikZ, который упрощает описание «interaction nets» в LaTeX. Кстати, такие GRS, как алгоритм Лэмпинга, а также нотация с помощью шин, строго говоря, не являются «interaction nets», хотя любая «interaction net» является GRS.

Вот пример правил для системы с агентами типов δ и γ арности 2:



\usepackage{tikz}
\usepackage{tikz-inet}
\tikzset{phantom/.style={fill=none, draw=none, text opacity=0}}
\newcommand{\interact}[2]{
\begin{tikzpicture}[baseline=(center.base)]
\matrix{\inetcell(top){#1}[D] \\
\node [phantom] (center) {c}; \\
\inetcell(bottom){#2}[U] \\};
\inetwirefree(top.left pax)
\inetwirefree(top.right pax)
\inetwirefree(bottom.left pax)
\inetwirefree(bottom.right pax)
\inetwire(top.pal)(bottom.pal)
\end{tikzpicture}}

... 

\begin{eqnarray*}
\interact{$\gamma$}{$\delta$} \rightarrow
\begin{tikzpicture}[baseline=(center.base)]
\matrix{\inetcell(deltal){$\delta$}[U] &
& \inetcell(deltar){$\delta$}[U] \\
& \node [phantom] (center) {c}; & \\
\inetcell(gammal){$\gamma$}[D] &
& \inetcell(gammar){$\gamma$}[D] \\};
\inetwirefree(gammal.pal) \inetwirefree(gammar.pal)
\inetwirefree(deltal.pal) \inetwirefree(deltar.pal)
\inetwire(deltal.left pax)(gammal.right pax)
\inetwire(deltal.right pax)(gammar.right pax)
\inetwire(deltar.left pax)(gammal.left pax)
\inetwire(deltar.right pax)(gammar.left pax)
\end{tikzpicture};
& \interact{$\delta$}{$\delta$} \rightarrow
\begin{tikzpicture}[baseline=(center.base)]
\matrix{\inetcell[phantom](up){$\delta$}[D] \\
\node [phantom] (center) {c}; \\
\inetcell[phantom](down){$\delta$}[U] \\};
\inetwirefree(up.left pax) \inetwirefree(up.right pax)
\inetwirefree(down.left pax) \inetwirefree(down.right pax)
\inetwirecoords(up.left pax)(down.left pax)
\inetwirecoords(up.right pax)(down.right pax)
\end{tikzpicture};
& \interact{$\gamma$}{$\gamma$} \rightarrow
\begin{tikzpicture}[baseline=(center.base)]
\matrix{\inetcell[phantom](up){$\delta$}[D] \\
\node [phantom] (center) {c}; \\
\inetcell[phantom](down){$\delta$}[U] \\};
\inetwirefree(up.left pax) \inetwirefree(up.right pax)
\inetwirefree(down.left pax) \inetwirefree(down.right pax)
\inetwirecoords(up.left pax)(down.right pax)
\inetwirecoords(up.right pax)(down.left pax)
\end{tikzpicture}.
\end{eqnarray*}

15 января 2012, 11:32

14 января 2012

David Sorokins WebLog

Первые впечатления о CAPI из LispWorks

Сегодня впервые использовал CAPI для создания каркаса будущего редактора диаграмм. Остался очень довольным. Местами не похоже на Windows Forms, WPF/Silverlight, Swing, SWT, но разобраться можно. Работает, что отрадно.

Еще очень радует среда LispWorksСейчас в проекте 250 килобайт кода на лиспе. Секунда или две после внесения изменений – и я уже вижу обновленное окошко моего редактора. Помню, как я мучился в ожидании, когда почти ту же самую задачу реализовывал на Scala с помощью IntelliJ Idea. Приходилось ждать целую вечность!

написал dsorokin (noreply@blogger.com) 14 января 2012, 20:59

13 января 2012

"Записки программиста"

Эволюция используемых языков программирования

На написание этой статьи меня вдохновили заметки На чём пишете? Дениса Филонова и Эволюция используемых языков Даркуса. В них авторы вспоминают, как они начинали программировать, какие языки программирования в каком порядке учили и какое впечатление эти языки на них произвели. Вот и я решил написать такую заметку. Уже во время составления ее плана я сделал [...]

написал Eax 13 января 2012, 10:00

Russian-speaking Scala Enthusiasts

Новости из мира Scala за неделю (13 Января 2012)

13 Января 2012

Новое

Вышел Equinox Weaving Launcher! Это компонент для Eclipse, добавляющий новый вид конфигураций для запуска (launch configuration), который позволяет запускать приложения для Eclipse и Junit-тесты с включенным Equinox Weaving (технология для комбинирования Equinox и AspectJ). Equinox Weaving Launcher был сделан для упрощения разработки Scala плагина для Eclipse, но может быть использован и при разработке других приложений для Eclipse, использующих технологию Equinox Weaving..

AKKA 1.3-RC6. Последняя предрелизная версия для ветки 1.3.

Вышел sbt-git-plugin 0.4, плагин для sbt для кроссплатформенной поддержки git.

Также вышел groll 1.1.1. Этот плагин для SBT позволяет навигироваться и просматривать историю изменений в git.

Стал доступен код проекта Scalding. Scalding представляет из себя Scala API для Cascading. Cascading – легковесная библиотека и API на Java поверх MapReduce от Apache Hadoop’s.

Вышел Lift 2.4 Final. В новой версии этого веб фреймворка куча новых добавлений и исправлений:

  • Улучшенная поддержка JSON.
  • Улучшения в Record.
  • Поддержка Squeryl/Record для Crudify.
  • Значительное улучшение поддержки для MongoDB (включая поддержку для записсей-ссылок и бинарных записей).
  • Поддержка BsonDSL (BSON типы теперь доступны в JsonDSL).
  • Улучшения в Mailer.
  • Улучшения в транформациях CSS-селекторов.
  • Улучшения в привязках сниппетов: привязка по подпакетам, на основе ресурса (Loc) для визардов и Screen.
  • Значительное улучшение поддержки REST включая чистые (stateless) Async/Continuations на веб-контейнерах Jetty 7, 8 и на Tomcat/Glassfish.
  • Возможность получить html5 шаблоны.
  • Модули локализации.

Новое в блогах

Еще большая по простоте масштабируемость с Akka и RegistryActor от Vasil Remeniuk (@remeniuk).

Пишем прозрачные тесты без повторов в коде от David Bryant Copeland (@davetron5000).

Graham Lea обновил свое Scala/Spring/Hibernate/Maven веб-приложение.

Еще о порядке разрешения неявных преобразований (implicits) от eed3si9n (@eed3si9n).

Простой, но полезный пост про эволючию обычного разработчика на Scala от Rahul Goma Phulore (@missingfaktor).

Heiko Seeberger (@hseeberger) обновил примеры для Akka для версии 2.0.

Еще один пост про сложность Scala Настоящая сложность Scala от Yang Zhang (@yaaang).

Daniel Sobral (@dcsobral) отвечает Янгу в своем посте Добавление методов к коллекциям в Scala.

Мартин Одерски также ответил Янгу.

Marc-Daniel Ortega (@patterngazer) написал про монады.

Josh Suereth (@jsuereth) начал раздавать в своем блоге награды наиболее активным участникам/проектам Scala community.

Alex Blewitt (@alblue) поговорил с Iulian Dragos (@jaguarul) о Scala IDE 2.0.

Предложения как сгладить кривую обучения Scala изложил Dhananjay Nen (@dnene) в посте Скале нужны террасы.

Josh Suereth (@jsuereth) сделал презентацию про SBT.

написал scala-enthusiasts-belarus@googlegroups.com (Scala Enthusiasts Belarus) 13 января 2012, 08:00