понедельник, 22 июля 2013 г.

Поиск по структурам молекул на основе Lucene и Chemical Development Kit

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

Размышления на эту тему привели к посту на habrahabr.ru и небольшому проектику на github. Возможно, кому-то пригодится.

вторник, 18 июня 2013 г.

Scala в гостях у Hadoop. Часть 2. Запускаем кластер на Amazon EC2.

В предыдущем посте я рассказвыл о фреймворке Scoobi. В качестве иллюстрации я написал небольшое MapReduce-приложение, которое вычисляет число π/4. Теперь я собираюсь рассказть о том, как запустить его на кластере Amazon EC2. В этом посте описана пошаговая процедура запуска Scoobi-приложения, начиная от аренды сервера на Amazon и заканчивая получением результата Монте-Карло-вычислений.


Для создания Hadoop-кластера будем использовать Apache Whirr. Для работы Whirr требует ssh-клиент, поэтому будем запускать его на Linux-машине. Лично у меня на компьютере стоит Windows, поэтому я арендую на Amazon-е ещё один Linux инстанс, с которого буду управлять Hadoop-кластером с помощью Whirr (ещё одним относительно простым решением было бы создать виртуальную Linux-машину на основе VMWare или VirtualBox).

Создаём машину в Amazon EC2 для админимтрирования кластера Hadoop

Итак, сначала арендуем машину для запуска Whirr. Тут нет ничего сложного. Регистрируемся на Amazon AWS. Затем идём в AWS Management Console, затем выбираем пункт EC2 Virtual Servers in the Cloud:
Жмём на кнопку "Launch instance". Затем выбираем "Classic wizzard" (хотя можно воспользоваться и другим мастером). Затем выбираем Amazon Linux AMI.


Дальше жмём "Continue". Так как нам надо достаточно мало ресурсов, только для администрирования кластера, а не для самих вычислений, из соображений самой низкой цены цены выбираем меняем "T1 Micro" на "M1 Small" в поле Instance Type:


Далее жмём несколько раз "Continue". Доходим до страницы, на которой нам предложат ввести теги (метки) для запускаемого инстанса. Они нужны главным образом для удобства. Не пренебрежём этой возможностью, определим имя инстанса (hadoopmanager):


Жмём ещё раз "Continue". На следующей странице предлагается создать пару ключей. Вводим удобное название (например, у меня - whirrkey) и нажимаем "Create & Download your Key Pair"


Сохраняем сгенерированный файл с расширением "pem", и ни в коем случае его не теряем. На следующей странице предлагается создать "Security group". Security group определяет по каким портам разрешено коннектится к инстансу. Как минимум необходимо открыть порт 22, чтобы можно было соединяться с сервером с помощью SSH. Если я не ошибаюсь, в существующих Security group-ах по умолчанию создаётся группа default, в которой тоже открыт доступ по SSH.
Настройки групп можно поменять потом, открыв нужные или закрыв ненужные порты.



Далее жмём "Continue", затем "Launch". Наблюдаем сообщение: "Your instances are now launching". Жмём "Close". Наблюдаем список запущенных инстансов с только что созданной нами виртуалкой.

Выбрав в списке созданную виртуалку, можно узнать её доменное имя, которое мы будем использовать для доступа к машине по SSH.


Теперь остаётся получить доступ к созданному серверу, например с помощью PuTTY. Для этого нам понадобится ключ whirrkey.pem, который мы сохранили.

Если использовать популярный Windows SSH-клиент PuTTY, то надо сначала запустить PuTTYGen. Запускаем PuTTYGen, загружаем сохранённые pem-файл, нажав кнопку "Load".


После того, как pem-файл загружен, нажимаем "Save private key", и сохраняем ключ в виде файла с расширением ppk (я назвал его whirrkey.ppk). 

Теперь можно использовать PuTTY для доступа к созданному серверу.

Вводим адрес нашей машины в поле "Host name (or IP address)":



Так же указываем наш ключ whirrkey.ppk для аутентификации:



 Жмём Open. Логинимся как пользователь ec2-user. Пароль вводить не потребуется.

Настраиваем окружение для администрирования Hadoop-кластера

В соответствии с документацией Scoobi для запуска нам понадобится Hadoop CDH4, выпускаемый компанией Cloudera. Это по сути тот же Apache Hadoop определённой версии, в который компания Cloudera добавляет наиболее важные багфиксы и фичи из последующих, возможно ещё не выпущеных релизов Hadoop. Дистрибутивы Clouder Hadoop (CDHx) можно скачать с сайта cloudera.com Компания зарабатывает на технической поддержке облачных технологий, основанных на Hadoop.

Разворачивать и конфигурировать кластер "по одной ноде" довольно хлопотно. К счастью, существуют средства вроде Apache Whirr, которые заметно облегчают этот процесс. Необходимо задать сколько и каких hadoop-нод мы хотим в своём кластере, а Whirr сделает всю рутинную работу: арендует под них машины на Amazon, развернёт и сконфигурирует на них hadoop. По этому пути мы и пойдём.

Устанавливаем Hadoop CDH4

>cd ~
>wget http://archive.cloudera.com/cdh4/one-click-install/redhat/6/x86_64/cloudera-cdh-4-0.x86_64.rpm
>sudo yum --nogpgcheck localinstall cloudera-cdh-4-0.x86_64.rpm
>sudo yum install hadoop-yarn-resourcemanager


Устанавливаем Apache Whirr:

>mkdir ~/opt

>cd ~/opt

>wget http://www.sai.msu.su/apache/whirr/whirr-0.8.2/whirr-0.8.2.tar.gz

>tar -xvf ./whirr-0.8.2.tar.gz

Конфигурируем Apache Whirr:

>export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID
>export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY

Значения $AWS_ACCESS_KEY_ID и $AWS_SECRET_ACCESS_KEY  можно узнать здесь: https://portal.aws.amazon.com/gp/aws/securityCredentials. Эти параметры можно указать не только через переменные окружения, но и в файле ...

Генерируем ключи:

ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa_whirr

Создадим в домашней директории файл hadoop.properties (имя файла может быть и другим, мы его будем указвать явно при вызове следующих команд). В этом файле и будут основные настройки нашего кластера. Вот его содержимое:

whirr.cluster-name=scoobicluster

whirr.instance-templates=1 hadoop-jobtracker+hadoop-namenode+ganglia-monitor+ganglia-metad,3 hadoop-datanode+hadoop-tasktracker+ganglia-monitor

whirr.java.install-function=install_openjdk
whirr.java.install-function=install_oab_java

whirr.hadoop.install-function=install_cdh_hadoop
whirr.hadoop.configure-function=configure_cdh_hadoop
whirr.yarn.configure-function=configure_cdh_yarn
whirr.yarn.start-function=start_cdh_yarn
whirr.mr_jobhistory.start-function=start_cdh_mr_jobhistory
whirr.env.REPO=cdh4
whirr.env.MAPREDUCE_VERSION=2

whirr.provider=aws-ec2
whirr.identity=${env:AWS_ACCESS_KEY_ID}
whirr.credential=${env:AWS_SECRET_ACCESS_KEY}
whirr.hardware-id=m1.small

whirr.cluster-user=whirr
whirr.private-key-file=/home/ec2-user/.ssh/id_rsa_whirr
whirr.public-key-file=/home/ec2-user/.ssh/id_rsa_whirr.pub

Теперь, когда все настройки заданы, остаётся запустить наш кластер:

Запускаем кластер

cd ~/opt/whirr-0.8.1
bin/whirr launch-cluster --config ~/hadoop.properties

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



Теперь нужно собрать непосредственно то, что будем запускать на кластере, то есть вычисление pi/4, о котором я писал раньше.

Устанавливаем  sbt + git, собираем Scoobi-проект


Теперь нам нужно собрать наше Scoobi-приложение, получив в результате jar-файл. Как и для многих других scala-приложений, для сборки Scoobi-программ используется SBT.

Устанавливаем git из репозитория:

>sudo yum install git

Устанавливаем SBT:

>cd ~/opt

>wget http://scalasbt.artifactoryonline.com/scalasbt/sbt-native-packages/org/scala-sbt/sbt//0.12.3/sbt.tgz

>tar -xvf sbt.tgz

>export PATH=$PATH:~/opt/sbt/bin

Скачиваем исходники с помощью git

>cd ~/
>git clone https://github.com/AlexanderSavochkin/MCwithScoobi.git

Теперь собственно сборка jar-файла

>cd ./MCwithScoobi
>sbt assembly

Если всё прошло окей, то в директории проекта появится поддиректория target, в которой поямвитя файл MCwithScoobi-assembly-0.1.jar. Его-то мы и будем запускать в hadoop-кластере.

Запускаем вычисления в кластере

cd target
hadoop jar MCwithScoobi-assembly-0.1.jar

В результате в текущей директории появится поддиректория output, в которой в файле с названием типа ch20out2-r-00000 будет храниться результат вычисления в кластере. Он представлен в примерно таком виде:

(1,ValueEstimator(100000,0.7852700000000121,1.686227133271277E-6,-2.051820047199726E-14))

Здесь число 0.7852700000000121 - наша оценка пи/4, а следующее число - 1.686227133271277E-6 - оценка квадрата отклонения (дисперсии) оценки числа π/4.

Останавливаем кластер

Когда кластер нам больше не нужен, остановить его можно командами:

cd ~/opt/whirr-0.8.2
bin/whirr destroy-cluster --config ~/hadoop.properties


Результат как обычно можно увидеть в панели управления AWS.

Заключение

Было показано, как арендовать машину на Amazon EC2 (с которой осуществлялось администрирование Hadoop-кластера), как запустить и остановить кластер с помощью Apache Whirr и как запустить в этом кластере Scoobi-приложение. Само собой разумеется, есть другие способы сделать то же самое. Например, если ваша локальная машина работает под Linux, вы можете пропустить шаг с арендой Linux-сервера, и запускать Whirr на своей локальной машине. Можно вместо аренды Linux-машины на Amazon использовать виртуальную машину, можно сконфигурировать кластер без Whirr и.т.д. Короче говоря, здесь показан лишь один из возможных путей.

Особенность нашего "игрушечного" MapReduce-приложения было то, что оно не использует входные данные, хранимые в распределённой файловой системы HDFS, и не сохраняет в HDFS результаты, поэтому рассмотрение кластера как именно распределённого хранилища данных осталось за бортом.  Большинство hadoop-приложений всё-таки используют какие-то "большие" входные данные. Возможно, как-нибудь позже я ещё вернусь к этой теме. Кроме этого в будущем хотелось бы коснуться возможности арендовать на Amazon-е машины с процессорами NVIDIA Tesla, которые предоставляют невиданную мощь для решения параллельных числодробительных задач.

Ссылки

Более подробне описание процедуры запуска Hadoop CDH4 с помощью whirr

http://www.cloudera.com/content/cloudera-content/cloudera-docs/CDH4/4.2.0/CDH4-Installation-Guide/cdh4ig_topic_4_4.html

среда, 15 мая 2013 г.

Scoobi: Scala в гостях у Hadoop


Пара вводных слов: Big data, Map-Reduce, Hadoop

Без сомнения, "big data" сегодня одна из "горячих" тем в мире IT.

Эти обстоятельства заставляют и меня посматривать в сторону соответствующих технологий и интересоваться новостями в области "больших данных".

В контексте "больших данных" часто всплывает концепция MapReduce. Это модель вычислений, которая подходит для определённых классов задач, в рамках которой вычисления могут производиться параллельно на большом количестве компьютеров, объединённых в кластер. Среди задач, эффективно распараллелеливаемых с помощью MapReduce, стоит упомянуть построение инвертированного индекса, вычисление PageRank, поиск, сортировку, некоторые графовые алгоритмы и алгоритмы из области машинного обучения и.т.д. Плодотворность этого подхода базируется на том, что "если данных очень много, проще перемещать вычисления, а не данные", поэтому важной частью MapReduce-фреймворка должно быть распределённое хранилище данных. Сущность MapReduce можно выразить в следующих шагах:

  • Шаг 1. Входные данные разбиваются на "куски", которые могут быть обработаны параллельно. Например:
        -входные данные - это множество отдельных файлов. Естественно попытаться рассматривать каждый файл как отдельный "кусок данных" и обрабатывать параллельно
        -входные данные - многострочный файл, каждая строка которого может быть обработана параллельно. Можно разбивать его на отдельные строки или на диапазоны строк.
        -входные данные - диапазоны чисел, для которых нужно провести вычисления
  • Шаг 2. К каждому "куску" применяется процедура "Map", результат которой -- множество пар ключ-значение (K, V).     Вычисления Map для разных данных выполняются параллельно (часто в кластере на разных машинах).
  • Шаг 3. Результаты группируются по ключу.
  • Шаг 4. Сгруппированные по ключу результаты направляются на вход процедуры Reduce, которая "сворачивает" данные для каждого ключа в единый результат. Если операция Reduce ассоциативна, то она может быть эффективно распараллелена.

Одно из широко применяемых средств для организации MapReduce-вычислений - Apache Hadoop. Это свободный фреймворк помогающий создавать и выполнять распределённые программы. Помимо реализации модели MapReduce, Hadoop поредоставляет распределённую файловую систему. Чтобы реализовать "своё" распределённое вычисление вам как правило нужно определить собственные классы Mapper и Reducer, и некоторый "инициализирующий процесс" код.

"Родным" языком Hadoop является Java, поэтому самый прямой путь - реализовать процедуры Map и Reduce на Java. Однако есть возможность использовать и другие языки программирования с помощью Hadoop Streaming.


Подобно тому, как изучение нового языка программирования обычно начинают с написания программы, выводящей на экран "Hello world", знакомство с MapReduce начинают с задачи подсчёта слов в тексте. Пусть в распределённой файловой системе лежит множество файлов - текстовых документов. Наша задача состоит в том, чтобы подсчитать, сколько раз встречается каждое слово во всех документах.

Наша процедура Map принимает на вход документ, разбивает его на отдельные слова и порождает для каждого слова w пару (w,1). Слово w здесь является ключом, 1 - значением (См. шаг 2 в описании Map-Reduce).

На вход нашаей процедуре Reduce подаётся слово и список . Этот список формируется из объединённых результатов всех выполнений процедуры Map. Если вы используете Hadoop, то вам не нужно явно заботиться об этой группировке. Это происходит автоматически, "за кулисами".

Для реализации некоторых типичных задач можно обойтись и без программирования на Java, воспользовавшись Apache Pig, который по сути тоже представляет собой язык программирования, но более высокоуровневый и изначально ориентированный на идеологию MapReduce. Можно скомбинировать PIG и Java, написав "высокоуровневую часть" на Pig, а некоторые детали реализовать в виде UDF (User-Defined Function), написанных на Java.


Scoobi. Делаем вещи проще и быстрее с помощью Scala.


Здесь я хочу описать другой путь, основанный на языке Scala и библиотеке Scoobi. Поскольку Scala совместима с Java, и из Scala можно просто напрямую использовать объекты Java, мы можем практически построчно портировать MapReduce-код c java на scala, однако такой подход на мой взгляд не раскрывает в полной мере преймуществ Scala.

Ориентированный на Scala открытай фреймворк Scoobi разработан в NICTA. Scoobi позволяет очень кратко и выразительно описывать Map-Reduce вычисления. Так, например, подсчёт разных слов в тексте запишется так (взято из примеров Scoobi):


import com.nicta.scoobi.Scoobi._

val lines = fromTextFile("hdfs://in/...")

val counts = lines.flatMap(_.split(" "))
                .map(word => (word, 1))
                .groupByKey
                .combine(_+_)

persist(counts.toTextFile("hdfs://out/...", overwrite=true))

Разберём этот код: важной частью API Scoobi является trait (аналог интерфейса Java) DList[T]. Этот тип представляет "распределённый список", который может жить на нескольких машинах Hadoop-кластера. Функция fromTextFile возвращает распределённый список строк файла типа DList[String]. К этому списку применяется метод flatMap(_.split(" ")) . В результате на выходе получаем распределённый список отдельных слов файла (того же типа:  DList[String]). К получившемуся списку применяется метод  map(word => (word, 1)) . Здесь мы генерируем распределённый список тех самых пар ключ-значение, о которых упоминалось выше, на шаге 2 описания вычислений MapReduce. Ключом у нас являетсяы слово текста, а "значением" -- 1. Метод groupByKey соответствует шагу 3.

Здесь я хочу привести свой собственный пример, использующий Scoobi: вычисление числа π (пи) методом Монте-Карло. Помимо вычисления оценки самого числа π, мы будем вычислять и среднеквадратичное отклонение этой оценки, что позволит нам судить о точности результата.

Для вычисления числа π мы будем генерировать пары (x,y) псевдослучайных чисел, каждое из которых принадлежит отрезку [0,1]. Площадь белой четверти круга на рисунке равна π/4, а площадь ограничивающего квадрата равна 1.



Следовательно, если бросать случайные точки в квадрат, то отношение числа всех точек к числу точек, попавших в четверть-круг будет стремиться к π/4. Это, конечно, весьма тривиальный и непрактичный пример применения метода Монте-Карло, но это в данном случае и хорошо, так как позволит сосредоточится в большей степени на технических деталях, а не на математике.

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

Для реализации этого подхода я создал scala-класс ValueEstimator.

//Класс "оценки числового значения".
//Содержит следующие поля:
//numTests - количество измерений
//mean - среднее значение
//variance - дисперсия
//meanError - накопленная ошибка
case class ValueEstimator(numTests:Long, 
                          mean:Double, 
                          variance:Double, 
                          meanError:Double) {
   //Комбинирует "этот" экземпляр ValueEstimator с другим,
   //Возвращает полученное значение оценки.
   def combineEstimations( other:ValueEstimator ):ValueEstimator = ...
}

//Это нужно написать чтобы Scoobi "понимал" наш класс
implicit val valueEstimatorFmt = mkCaseWireFormat(ValueEstimator, 
                                        ValueEstimator.unapply _)


Это позволит мне продемонстрировать ещё одну мощную возможность Scoobi: распределённый список DList может содержать элементы не только каких-то предопределённых типов, но и наших case-классов.

И так, вот наша реализация вычисления методом Mонте-Карло с помощью Scoobi:


package scoobimc

import scala.util.Random
import com.nicta.scoobi.Scoobi._
import com.nicta.scoobi.io.func.FunctionInput

import MCUtils._

object PiMonteCarlo extends ScoobiApp {

  val numSequencies = 10
  val generateSequenceLength = 10000

  def run() = {
    val input = FunctionInput.fromFunction(numSequencies)( x=>x )

    val pi = input.flatMap( generateRands _ )
             .map( checkIfInside )
             .groupByKey
             .combine( 
                (x:ValueEstimator, y:ValueEstimator) => 
                    x combineEstimations y 
              )

    //Сохраняем результат
    persist( toTextFile(pi, "output", overwrite=true))
  }

  //Проверяют, что точка x лежит внутри единичного четверть-круга
  def checkIfInside( x:(Double, Double) ) =
    if ( (x._1 * x._1) + (x._2 * x._2) < 1.0 )
      (1,ValueEstimator(1, 1.0, 0.0,0.0))
    else
      (1,ValueEstimator(1, 0.0, 0.0,0.0))

  //Генерирует случайные точки в единичном квадрате
  def generateRands( seed:Int ) = {
    val rand = new Random(seed)
    (1 to generateSequenceLength) map ( _ => (rand.nextDouble(),rand.nextDouble()) )
  }
}
как и в случае с подсчётом слов, разберём, что же здесь происходит. Вызов FunctionInput.fromFunction(numSequencies)( x=>x ) генерирует DList c последовательностью целых чисел от 0 до numSequencies. Затем к этому списку применяется метод flatMap( generateRands _ ) , который сопоставляет каждому числу списка последовательность псевдослучайных двумерных точек, длинной generateSequenceLength. Функция flatMap выдаёт эти последовательности в сконкатенированном виде, то есть единым DList-ом, который имеет длинну numSequencies*generateSequenceLength. К этому результату применяется метод map( checkIfInside ). На выходе, как обычно, получается DList, хранящий пары ключ-значение, где ключ - еденица, а значения -- обекты типа ValueEstimator. Для точек, попавших в четверть-круг, оценка 1, для непопавших - 0.

Нам остаётся найти среднее значение всех числовых оценок, которое должно стремиться к π/4.

Операция groupByKey в данном случае тривиальна, так как все элементы имеют один и тот же ключ, равный единице. Метод combine скомбинирует все наши ValueEstimator-ы, используя определённый нами метод combineEstimations. На выходе получим оценку среднего и дисперсии искомой величины, π/4. Переменная pi будет представлять собой маленький DList с единственной парой ключ-значение. Ключ - единица, значение - окончательный ValueEstimator числа π/4.

Надеюсь, этим постом мне удалось показать, что Scoobi является очень выразительным средством для организации распределённых MapReduce вычислений.

В одном из следующих постов я планирую написать, как запустить описанное выше вычисление числа π на кластере в Amazon EC2 из нескольких машин.

Ссылки

https://github.com/AlexanderSavochkin/MCwithScoobi - здесь выложены исходные коды к этому посту
Introducing Scoobi and Scalding: Scala DSLs for Hadoop MapReduce
Comparison of Hadoop Frameworks - обзор и сравнение фреймворков для Hadoop. Упоминается Scoobi и ряд альтернатив, ориентированных на Java/Scala/Clojure...

понедельник, 4 марта 2013 г.

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

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

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


Видео ускорено в 4 раза.


понедельник, 28 января 2013 г.

Реализация на Scala графической среды для языка Unlambda

Реализация на Scala графической среды для языка Unlambda

Для тех, кто меня знает, не секрет, что в свободное от изготовления спектроскопов и телескопов время я занимаюсь программированием. Собственно, сейчас это и есть моя основная деятельность. На этом поприще, конечно, тоже немало всего интересного, о чём можно написать пост. Некоторое время я раздумывал, организовать ли мне два разных блога: в одном из которых я бы иногда писал про всяческие поделки/опыты/явления, а в другом - о программировании, информационных технологиях и.т.п. Я решил писать обо всём в одном месте: во-первых мне так проще; во-вторых я и так пишу достаточно редко: пусть лучше будет один полуживой блог чем два неживых; ну а в третьих - нередко самое интересное именно "на стыке" областей: непонятно, куда постить, скажем, про кофейник, управляемый через веб-интерфейс :) 
В этом посте речь пойдёт о комбинаторной логике, основанном на ней языке программирования Unlambda, который может сломать вам мозг, и реализации "среды" для Unlambda на языке Scala, который, напротив, очень приятен.

Представим, что у нас есть язык, в котором существует единственный тип объектов: функции одного аргумента. И этот самый аргумент функций, и возвращаемое значение - тоже ничто иное, как функции одного аргумента. То есть вообще нет ничего другого - ни чисел, ни переменных, соответственно, нет и присваиваний. Есть, правда, один оператор: применить функцию взяв в качестве аргумента другую функцию. Запись (a b) означает "применить к a аргумент b". Будем считать применение левоассоциативным, то есть

 a b c = ((a b) c).

Для того, чтобы начать что-то конструировать из этого "материала", нам потребуются элементарные кирпичики: базовые функции, через которые мы будем выражать всё остальное. Вот они, K и S:

K x y = x
S x y z = x z (y z)

Минуточку! У нас же функции одного аргумента, что же означает запись K x y = x ? Очень просто: результат применения (K x) - функция, которая при применении к любому y вернёт x. В таком же ключе следует понимать и S x y z.

Всё! Этого достаточно, чтобы наша система была полной по Тьюрингу.
Грубо говоря, это значит, что оператор применения и комбинаторы S и K можно положить в основу языка программирования, на котором можно выразить все те же самые вычисления, что и на других, более привычных языках.

Чтобы понять, как с помощью комбинаторов S и K выразить такие вещи, как числа, ветвления, рекурсию и списки - рекомендую обратиться к популярной статье.

Всё это и положил в основу Unlambda изобретатель данного языка - Дэвид Мэдор (David Madore). Синтаксис языка очень простой, похожий на то, что я уже писал, только операция применения записывается не скобками, а обратным апострофом: Так, выражение ((ab)c) запишется как ``abc, выражение a(bc), соответственно, как `a`bc.

В Unlambda встроены уже знакомые нам функции s и k. Кроме них есть функция i, "тождественный комбинатор":

  I x = x

или, в синтаксисе Unlambda `ix = x. Её существование удобно, но необязательно, так как она может быть выражена чере S и K. Один из способов:

 S K K x = K x (K x) = x

 Для печати результатов служит функция .c, которая, будучи применённой к любому x вернёт x и напечатает символ c. Есть ещё с пяток функций, о которых можно прочесть, например, в статье в википедии.

Я хочу рассказать о созданной мной "графической среде", в которой можно исследовать и отлаживать программы на Unlambda. Интерфейс программы очень простой: там, где написано "Source code" пишем или вставляем из буфера обмена ваш исходный код. Например, на скриншоте ```s`kiii  . Затем, для того, чтобы провести разбор вашей программы и построение дерева вычислений (которое можно видеть в области "Evaluation tree"), нажимаем F2. Затем, если ваша программа читает какие-то данные (см. Википедию про чтение данных в Unlambda), то вводим данные для чтения программой в поле "Input". Теперь, для того, чтобы ваша программа выполнялась шаг за шагом, нажимаем F7. При этом будет видно, как шаг за шагом преобразуется дерево "Evaluation tree".



Где взять саму Unlambda-среду? Можно собрать из исходников, которые выложены на гитхабе.
Всё написано на языке Scala. Сборка осуществляется с помощью Simple Build Tool (SBT). Simple Build Tool - конечно же оксюморон, поэтому ниже приведу последовательность действий (в основном это команды в консоли), которые нужно выполнить, чтобы собрать и запустить мою Unlambda-среду.

Для тех, кто не хочет заморачиваться и доверяет собранным чужими руками бинарникам, я положил на Dropbox архив, в котором всё лежит уже в скомпилированном виде, и можно запустить среду, выплонив файл run.bat (в Windows) или run.sh (в Linux).

А вот инструкция для сборки из исходников:
  1. Ставим SBT. Скачать можно отсюда. Я заметил, что SBT плоховато работает в Windows, если имя пользователя Windows написано кириллицей.
  2. Качаем исходники ScalaSwingTreeWrapper. Можно так же воспользоваться git-ом и склонировать репозиторий. Например, у меня ScalaSwingTreeWrapper распаковался в директорию ScalaSwingTreeWrapper-master.
  3. Собираем ScalaSwingTreeWrapper. В оболочке командной строки переходим туда

    cd ScalaSwingTreeWrapper-master
    и выполняем команду

    sbt
    если sbt не прописана в переменной окружения PATH, то вместо этого

    ПОЛНЫЙ_ПУТЬ_К_SBT/sbt
    В оболочке sbt выполняем команды

    update
    package


    В результате в директории появится файл ScalaSwingTreeWrapper-master/target/scala_2.9.1/scalaswingtreewrapper_2.9.1-1.2.jar.Возможно цифры, обозначающие версии компилдятора Scala и  ScalaSwingTreeWrapper'а будут отличаться.
  4. Качаем исходники среды Unlambda, о которой я здесь пишу. Как и в случае с ScalaSwingTreeWrapper'ом можно так же воспользоваться git-ом и склонировать репозиторий. Распаковываем архив, получаем директорию UnlambdaSimpleEnvironment-master
  5. Копируем файл scalaswingtreewrapper_xxx.jar, полученный на шаге 3 в поддиректорию проекта UnlambdaSimpleEnvironment-master/Unlambda-GUI/lib.
  6. Собираем и запускаем среду Unlambda. Для этого переходим в директорию UnlambdaSimpleEnvironment-master, и запускаем sbt, выполнив команду
    sbt

    В оболочке sbt выполняем команду

    gui/run
Так. А зачем всё это было нужно мне?

Я уже давно присматривался к языку программирования Scala. Уже несколько лет назад я вдоволь написался стандартных примеров в духе "здравствуй мир", вычисления факториала и.т.п. Недавно на сайте coursera.org проходил онлайн-курс по Функциональному программированию на Scala за авторством самого Мартина Одерски (Martin Odersky), создателя Scala. Ознакомившись с материалами курса, я решил, что неплохо было бы более близко познакомится с "экосистемой" Scala, сварганив пусть "маленький и несерьёзный", но "настоящий" проектик, в котором бы использовалась система сборки (в мире Scala стандарт де-факто SBT), автотесты и.т.п. Кроме того, я между делом интересуюсь теоретическими основами функционального программирования. Поэтому
ещё одна реализация Unlambda'ы - идеальный вариант: написать на Scala интерпретатор Unlambda крайне просто, других реализаций именно на Scala я не нашёл (хотя вообще реализаций Unlambda довольно много). Кроме того, мало таких реализаций, в котором можно было бы не только просто выполнять программы на Unlambda, а в интерактивном режиме смотреть, что и как происходит "внутри" вашей программы. Эти обстоятельства и смотивировали меня создать описанную Unlambda-среду используя Scala.

Ссылки

  1. Комбинаторы — это просто! (wikibooks.org) - пожалуй, самое лучшее из того, что подходит для первого знакомства с комбинаторной логикой.
  2. Домашняя страничка языка Unlambda. В "стандартную поставку", которую можно скачать с этого сайта входят несколько реализаций интерпретатора на разных языках. Хотелось бы отметить реализацию на Perl, которая позволяет выполнять программы пошагово, в режиме отладчика с помощью интерфейса командной строки (как в GDB), и очень выразительные реализации на двух диалектах ML.
  3. Unlambda (ru.wikipedia.org) - статья в википедии
  4. Unlambda - мозги навыворот - похожий пост об Unlambda. Реализация на языке Go.
  5. Бестиповое лямбда-исчисление, комбинаторы, Unlambda и числа Фибоначчи - пост про Unlambda на habrahabr.ru. 
  6. Unlambda.NET - реализация Unlambda на C#
  7. Домашняя страничка и документация SBT.
  8. Getting started with Scala using SBT