Libretto для веб-приложений

10.4.2016 16:48

Libretto для веб-приложений

01 Начало работы

02 Как изучать примеры

03 Имена в Libretto

04 Типы данных

Базовые структуры данных

Строки String

Целые числа Int

Вещественные числа Real

Структура Any

Структура Unit

Последовательности

Кардинальности

Что такое тип данных Libretto

Пользовательские структуры

Мэпы

Структуры – объединения

XML и HTML

05 Операторы

Оператор блока

Переменные и операторы присваивания

Оператор if

Case–блок

06 Пути

Оператор точки

Путь

Символ $

Оператор as

Оператор index

Пустая последовательность

Вспомогательный блок

07 Методы

Контекст метода и ключевое слово this

Аргументы метода

Методы и последовательности

Тип результата метода

Полиморфные определения методов

Оператор return

Специальные методы

Сортировка

Индексы

Фильтр

Фильтр структуры

Ловушка пустоты

Комбинирование коллекционных методов

Анонимные методы

Анонимные методы без аргументов

Замыкание

Анонимные методы с аргументами

Оператор return в анонимных методах

Синтаксический сахар

Двоеточие

Звездочка (коллекционный контекст)

Инфиксный оператор

Решетка

08 Пакеты в Libretto

Структура пакета

Расположение пакетов в файлах

Импорт пакетов, использование методов и структур из других пакетов

Импорт библиотек

01 Начало работы

Это краткое описание Libretto – языка программирования, ориентированного на создание веб-приложений. Libretto устроен таким образом, что на нем можно разрабатывать все три основные компоненты веб-приложения:

  • сервер,
  • клиент,
  • базы данных.

При работе на Libretto нет нужды в использовании зоопарка разношерстных инструментов, которые обычно сопровождают веб-разработку.

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

Объекты баз данных также являются обычными структурами Libretto. Поэтому нет нужды в SQL и других внешних механизмах управления базами данных.

Необходимость в использовании Javascript также возникает крайне редко и далеко не во всех облачных приложениях.

Единое пространство программирования на сервере, клиенте и в базах данных, вкупе с исключительной компактностью языка позволяет повысить эффективность разработки и поддержки веб-приложений на Libretto в среднем в 4–5 раз.

02 Как изучать примеры

Libretto ориентирован на создание веб-приложений. Однако на нем удобно писать и обычные ("автономные") программы. Libretto является полноценным языком со статической типизацией. Его эффективность примерно соответствует эффективности Java и Scala.

Когда Libretto–программа исполняется автономно, вычисления всегда начинаются с метода main. Результатом выполнения программы является результат вычисления main.

Пример автономной программы на Libretto:

В ней определен метод fact, считающий факториал числа. Метод main вычисляет факториал нескольких чисел.

В Libretto имеется два типа комментариев:

  • однострочный //..., который начинается с двойной косой черты и завершается концом строки
  • многострочный /* ... */

Примеры комментариев:

2 + 2 // Это однострочный комментарий
/*
  Это многострочный
  комментарий
*/

Чтобы запустить пример с факториалом на счет, нажмите кнопку "Run". Код можно откорректировать и запустить заново. Кнопка "Reset" восстанавливает первоначальный код.

Этот код можно также скопировать в редактор консоли Libretto IDE и там нажать кнопку "Run".

03 Имена в Libretto

Имена в Libretto используются для обозначения переменных, методов, параметров, пакетов и т.д. Имеется два вида имен:

  • последовательность букв, цифр и знака подчеркивания, начинающаяся с буквы
    abc, a12, my_variable 
    

  • последовательность любых символов в обратных кавычках ("\u0060")
    `115`, `метод со сложным именем` 
    

04 Типы данных

Базовые структуры данных

К базовым структурам данных относятся строки String, целые числа Int, вещественные числа Real, а также структура Unit с единственным элементом unit.

Строки String

Libretto работает только в кодировке Unicode. Libretto предлагает несколько вариантов предствления строк.

  • Обычные строки

Например,

"Всем привет!\n"
В обычных строках можно использовать стандартные символы эскейпинга:

\n line feed
\r return
\t horizontal tab
\\ backslash
\" double quote
\b backspace
\f form feed
а также Unicode–представление символов.

"\u00A9" эквивалентно "©"

  • Строки-имена

Иногда строки состоят только из имени или идентификатора, например, "Hello". Для таких строк Libretto предлагает более компактное представление с использованием открывающей одинарной кавычки:

'John    'a_123

Строки-имена должны начинаться с буквы и могут содержать буквы, цифры и символ подчеркивания.

  • Скобочные строки (тексты)

Скобочные строки заключаются в открывающую '«' и закрывающую '»' французские кавычки. Вместо них можно также использовать двойные символы '<<' и '>>'.

Скобочные тексты удобны тем, что они могут состоять из нескольких строк и иметь вложенную структуру.

<<
<<Здравствуй>>, сказал я.
<<Привет>>, грустно ответил она.
«Отчего такое "веселое" настроение?», спросил я.
>>

Эскейпинга для скобочных строк не предусмотрено.

  • Параметрические строки

Если в строку включен блок #{<выражение на Libretto>}, то это выражение может оцениваться с помощью оператора "восклицательный знак" !. Например,

"2 + 2 = #{2+2}"! == "2 + 2 = 4"

  • Эквивалентность представлений строк

Следующие строки равны между собой

"Planet"   'Planet   <<Planet>>   «Planet»

  • Операции на строках

Базовые операции на строках

  • сложение (конкатенция) '+' ("abc" + "def" == "abcdef")
  • умножение на коэффициент '*' ("abc" * 3 == "abcabcabc")

На строках определены стандартные операции сравнения ==, !=, >, <, >=, <=.

Со строками работает целая группа библиотек, например,

  • libretto/text – системная библиотека работы с текстами
  • com/teacode/string – библиотека, содержащая комплект наиболее распространенных методов работы со строками (типа substring, indexOf, length и т.д.)
  • com/teacode/regexp – поддержка регулярных выражений
  • com/teacode/sinput – набор инструментов для разбора текстов и написания парсеров (краткое описание библиотеки находится здесь)
  • com/teacode/peg – реализация Parcing Expression Grammar
  • com/teacode/wiki – парсер wiki-документов в формате расширенного MultiMarkdown. Возможности wiki-документов, поддерживаемых библиотекой, описаны здесь. Текст, который вы сейчас читаете – это тоже wiki-документ, конвертированный с помощью библиотеки в веб-страницу.

и другие.

Целые числа Int

Могут быть положительными и отрицательными, и иметь любую длину.

1 -115 123798427349824729183471234123874291872934712934712938471298347

Базовые операции на целых числах:

  • сложение '+' (123 + 777 == 900)
  • вычитание '-' (123 - 777 == -654)
  • умножение '*' (123 * 777 == 95571)
  • деление нацело 'div' (115 div 3 == 1)
  • остаток от деления нацело 'mod' (115 mod 3 = 1)
  • взятие обратного '-' (-123)

На целых числах также определены стандартные операции сравнения ==, !=, >, <, >=, <=.

Вещественные числа Real

В Libretto используются вещественные числа с плавающей точкой:

-1.23    3.14159265358979323846264338327950288419716939937510

Базовые операции на целых числах:

  • сложение '+' (3.1415926 + 2.7182818284 == 5.8598744284)
  • вычитание '-' 3.1415926 - 2.7182818284 == 0.4233107716)
  • умножение '*' (3.1415926 * 2.7182818284 == 8.53973407681590984)
  • деление 'div' (3.1415926 div 2.7182818284 == 1.155727330101442692629964828999333)
  • взятие обратного '-' (-3.1415926)

На вещественных числах также определены стандартные операции сравнения ==, !=, >, <, >=, <=.

Структура Any

Any – структура "всех объектов". Любой экземпляр любой структуры является также экземпляром Any.

Структура Unit

Структуру Unit представляет только один элемент (экземпляр) – unit. Он используется в ситуациях, когда нужно вернуть значение, но неважно какое. Например, unit играет роль истины в предикатах (роль лжи исполняет пустая последовательность ()):

unit также играет роль контекста в тех выражениях, у которых контекст явно не определен (см. пункт Пути):

Здесь явно не указно, в каком контексте вычисляется duplicate. В таких ситуациях вычисляется выражение unit.duplicate.

Последовательности

Последовательность – упорядоченный набор произвольных структур, представленный в круглых скобках через запятую. Примеры:

(1,2,3,4,5)
("abc", 4, 3.14, "hello!")

Последовательности в Libretto плоские – они не могут быть вложены друг в друга. Следующих три выражения равны последовательности (1,2,3,4,5):

(1, (2, 3), (4, 5)) == ((((1,2),3),4),5) == (1,2,3,4,5)

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

Пример с факториалом в начале также основан на этой особенности Libretto.

Элемент и одноэлементная последовательность в Libretto не отличаются:

(10) == 10
Это означает, что отдельный элемент также является частным случаем последовательности.

Пустая последовательность – последовательность (), не содержащая ни одного элемента.

Кардинальности

В Libretto можно контролировать размер последовательностей. Для этого используются следующие символы, называемые кардинальностями:

  • ! – ровно одно значение
  • ? – ноль или одно значение
  • * – последовательность из любого количества значений
  • + – последовательность из одного или более значений.

Что такое тип данных Libretto

Кардинальности вместе с именами структур определяют в Libretto типы данных. Примеры типов данных:

  • String! – одиночное строковое значение
  • Int? – не более одного целого числа (пустая последовательность, либо число)
  • Real* – последовательность из любого количества вещественных чисел
  • Any+ – любая непустая последовательность.

Пользовательские структуры

Разработчик может определить собственные структуры данных, например,

В этом примере определяется структура Person с двумя полями – name и surname. В методе main создается экземпляр структуры "Персона" для Александра Македонского, а затем берется значение его поля name.

Структуры в Libretto – неизменяемые. Однажды заданные значения полей изменить нельзя. Для хранения изменяемых значений используются мэпы.

Мэпы

"Мэп" – абстрактная структура, хранящая данные в виде пар "ключ / значение". Для задания и получения значений ключей используется метод "!". И ключом, и значением может быть любая структура Libretto.

Примеры работы с ключами из значениями мэпов:

struct Person(name:String!, age:Int!)

// присваивание значений ключам

map!'name = 'Маша
map!'age = 16
map!'Маша = Person('Маша, 16)
map!'sisters = ('Даша, 'Глаша)

fix sasha = Person('Саша, 20)
map!sasha = 'Петров

// получение значений

map!'name // == 'Маша
map!'age // == 16
map!'Маша // == Person('Маша, 16)
map!sasha // == 'Петров
map!'sisters // == ('Даша, 'Глаша)

map.keys // = ( 'name, 'age, 'Маша, Person('Саша, 20) )
map.new // новый мэп того же типа, что и map
Методы, действующие на мэпах

  • map.keys возвращает все ключи мэпа
  • map.new создает новый экземпляр мэпа того же типа, что и map.

В Libretto имеется несколько реализаций мэпов. В веб-разработке в основном используются мэпы, реализованные в библиотеках

  • com/teacode/map – реализация мэпов в оперативной памяти
  • com/teacode/base – транзакционная реализация мэпов (мэповые базы данных)

Существенным качеством мэпов является бестиповость: любому ключу можно присвоить любое количество любых структур:

map!'mix = (1, 'Маша, Person('Маша, 16), 3.14)

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

Опишем родственников:

В примере определены три персоны – Пол, Энн и Мэри. Энн – жена Пола, а Мэри – сестра Энн. Программа узнает, кто является свояченицей (сестрой жены) Пола.

Здесь

  • map/m – метод библиотеки com/teacode/map, создающий новый мэп.
  • Мэпы могут быть значениями ключей других мэпов.
  • M – имя структуры мэпов в библиотеке com/teacode/map. Составное имя map/M означает, что используется структура M из библиотеки com/teacode/map.

Абсолютной рекомендацией является инкапсуляция бестиповых средств работы с мэпами внутри специальных методов. Это делает код более безопасным, читабельным и управляемым:

Определение типизированных методов wife, sister и name позволило нам очень компактно записать решение задачи.

  • Метод to.

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

map.to(key, value)  // ключ key в мэпе map получает значение value
Благодаря синтаксическому сахару это можно также записать так:
map {key to value}
Использование метода to позволяет нам записать программу в существенно более компактном и читабельном виде:

  • Удаление ключа из мэпа

Для удаления ключа, достаточно присвоить ему пустую последовательность

Структуры – объединения

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

struct Number = Int | Real

def main = {
  var x: Number = 1
  x = 3.14
}
Переменной x можно задавать как целые, так и вещественные значения.

В следующем примере структура Node объединяет две реализации мэпов – из библиотек com/teacode/map и com/teacode/base. Благодаря этому метод getmap может работать как с обычными мэпами, так и с мэпами в базах данных.

Программа работает как с обычными мэпами map1 и map2, так и с объектами дефолтной базы данных Libretto.

Методы управление базой данных:

  • base/memo обеспечивает доступ к базе "по умолчанию",
  • base/w – открывает в этой базе транзакцию
  • base/new создает новый мэп в базе.

XML и HTML

Структуры XML являются примером пользовательских структур. Они определены в системной библиотеке libretto/xml. Особенностью этих структур является то, что для них в рамках Libretto предусмотрен специальный синтаксис. Например,

Эта запись эквивалентна следующей записи с явным использованием структур из libretto/xml:

В обеих программах вычисляются значения поля attrs (это атрибуты), у которых, в свою очередь, берется имя name.

XML–выражения могут быть параметризованы с помощью вставок вида #{<выражение Libretto>}. Например,

В данном примере последовательность фамилий была развернута в неупорядоченный список HTML. Здесь string – метод, возвращающий строковое представление структуры.

В Libretto может быть представлен произвольный XML. Например, можно описать виды растений:

def main = 
<CATALOG>
  <PLANT>
    <COMMON lang="en">Bloodroot</COMMON>
    <COMMON lang="ru">Лапчатка</COMMON>
    <BOTANICAL>Sanguinaria canadensis</BOTANICAL>
    <ZONE>4</ZONE>
    <LIGHT>Mostly Shady</LIGHT>
    <PRICE>2.44</PRICE>
    <AVAILABILITY>031599</AVAILABILITY>
  </PLANT>
  <PLANT>
    <COMMON lang="en">Marsh Marigold</COMMON>
    <COMMON lang="ru">Калужница болотная</COMMON>
    <BOTANICAL>Caltha palustris</BOTANICAL>
    <ZONE>4</ZONE>
    <LIGHT>Mostly Sunny</LIGHT>
    <PRICE>6.81</PRICE>
    <AVAILABILITY>051799</AVAILABILITY>
  </PLANT>
</CATALOG>
Однако в первую очередь xml используется для формирования компонент html страниц.

Html страницы и их компоненты представляются в Libretto в строгом xml формате.

В частности, это означает, что каждому открывающему тегу должен соответствовать закрывающий (например, абзац <p>...</p>), а элементы без контента должны закрываться косой чертой в конце, например,

  • <img src="pic1.png"/>
  • <link rel="stylesheet" type="text/css" href="/main.css"/>.

05 Операторы

Оператор блока

Оператор блока – последовательность выражений в фигурных скобках, разделенных точкой с запятой:

Значением блока является значение его крайне правого выражения (x + y в примере).

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

Переменные могут быть определены только внутри блоков.

Контекст блока является контекстом каждого его выражения:

Блок часто используется как тело функции, например, в определении sum:

В случае блока символ равенства между заголовком и телом функции может быть опущен:

Переменные и операторы присваивания

В Libretto поддерживаются только локальные переменные, действующие внутри блоков. Имеется два типа переменных:

  • fix – присваиваемое значение не может быть изменено
  • var – присваиваемое значение может быть изменено в дальнейшем.

Рекомендация в максимальной степени использовать неизменяемые переменные fix, поскольку они более эффективные и в большей степени позволяют вылавливать ошибки программирования.

На практике переменные fix используются в 90% случаев. Переменные var требуются только в случаях постепенного накопления данных, а также для фиксации смены состояний программы.

В Libretto имеется три вида оператора присваивания:

  • x = newval – базовый оператор присваивания, инициализирующий или изменяющий значение переменной x.
  • seq += (1,2,3) – добавляет (1,2,3) в конец последовательности, содержащейся в seq.
  • seq .= (1,2,3) – добавляет (1,2,3) в начало последовательности, содержащейся в seq.

Операторы присваивания += и .= работают только на изменяемых переменных, имеющих кардинальность * или +.

Аналогичным образом операторы присваивания работают на мэпах, изменяя значения ключей:

Оператор if

Стандартный условный оператор, возвращающий значение в зависимости от значения условия.

Условие ложно, если оно равно пустой последовательности. В противном случае условие истинно.

Ветка else может опускаться. В этом случае ее значение равно ():

Case–блок

Case позволяет осуществлять разбор случаев:

Работают два вида проверок:

  • на совпадение значения, например, case 1 => ....
  • на соответствие типу, например, case i:Int => i + 100. В этом случае переменная i имеет тип Int!.

Подчеркивание "_" соответствует любому значению.

06 Пути

Оператор точки

Оператор точки c.exp вычисляет выражение exp в контексте значений c.

В этом примере контекст 1 подается методу add1.

Если в контексте находится более одного значения, то оператор точки превращается в итератор, который последовательно "подает" значение за значением в качестве контекста:

Если в контексте находится пустая последовательность, то выражение после точки ни разу не выполняется:

Здесь выражение после точки содержит ошибку – деление на ноль. Однако поскольку контекст пустой, то до ошибки дело не доходит и вычисление останавливается на x.

Путь

Цепочка применений оператора точки называется путем, например,

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

Символ $

Для доступа к контексту внутри пути используется символ доллара $:

На каждом шаге у $ свое значение:

Здесь первый $ равен 10, а второй – 15.

Оператор as

Оператор as присваивает переменной текущее значение шага. Эта переменная действует до конца пути:

Используя as можно увидеть, в каком порядке путь перебирает элементы последовательностей:

Видно, что в пути осуществляется перебор "в глубину".

Оператор index

Оператор index присваивает переменной индекс текущего элемента последовательности шага:

Нумерация элементов начинается от 0.

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

Оператор index локален: для каждого контекстного значения шага нумерация начинается заново:

Пустая последовательность

Пустая последовательность играет важную роль для вычислений в пути. Если некоторый шаг принимает значение (), то вычисление дальше не идет:

В данном примере шаг even становится пустым для нечетных чисел в контексте. Поэтому для них вычисление квадрата не делается.

Часто удобно интерпретировать пустоту на некотором шаге как неудачу (failure) в вычислениях для данного значения контекста. Пустоту шагов можно обрабатывать. Для этого существует специальная "ловушка пустоты" ?(..).

Вспомогательный блок

Для блока в пути есть две возможности:

  • быть отдельным шагом пути
  • быть дополнением к другому выражению внутри шага.

В первом случае блок меняет контекстное значение, во втором – не влияет на него:

В этом примере: для res1 блок {$ + 2} является отдельным шагом, его значение является значением всего пути. Для res2 блок является дополнением к 5 (между ними нет точки). Здесь блок не влияет на значение всего выражения. Блок второго вида называется вспомогательным.

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

Здесь блок никак не влияет на результат вычисления.

В качестве еще одного примера использования вспомогательных блоков реализуем глобальный счетчик. Счетчик организуем в базе данных, работающей в оперативной памяти (управление этой базой реализовано в библиотеке libretto/mem). Для работы со счетчиком введем структуру Counter "оборачивающую" мэп базы данных. Метод get возвращает текущее значение счетчика, хранящегося в мэпе. Если значение счетчика отсутствует, то он инициализируется. Метод add увеличивает значение счетчика на 1.

Предположим, что нам нужен метод, возвращающий удвоенное значение счетчика, и при этом увеличивающий его:

Определены два метода, решающие эту задачу – oper1 и oper2. Первый метод реализован в стандартном стиле, а второй использует вспомогательный блок. Разница видна невооруженному глазу.

Такой же блок используется при инициализации счетчика в методе getcount. Инструменты для работы с базами данных и ловушка пустоты .?(...) описаны ниже.

07 Методы

Определение метода начинается с ключевого слова def, после которого идет заголовок метода, а затем его тело:

def hello = "Привет!" 
Заголовок состоит из имени метода hello. Строка "Привет!" – тело метода. Заголовок и тело метода разделяются знаком равенства.

Метод можно вычислить:

Контекст метода и ключевое слово this

Методы, как и любые другие выражения Libretto, всегда вычисляются в контексте. Контекст метода может быть типизирован. Это делается сразу после ключевого слова def:

def Int add5 = this + 5
Метод add5 определен только на целых числах. Ключевое слово this позволяет получить текущее значение контекста метода:

Описание типа контекста может быть опущено. Тип по умолчанию – Any:

Определения id1 и id2 эквивалентны.

Пользовательские структуры также могут служить контекстом метода. В Libretto определение методов на структурах играет примерно такую же роль, как определение методов на классах в Java. Например,

Метод fullname работает в контексте персон, порождая строчку с полным именем персоны.

Аргументы метода

Метод может иметь любое количество аргументов, например:

В этом примере для каждого аргумента явно задан его тип данных (вид структуры и кардинальность). Первый аргумент strings может получать на вход произвольную последовательность строк, а второй аргумент – suf – равно одну строку.

Компилятор во время трансляции программы проверяет, соответствует ли тип актуальных параметров описаниям. Если нет, то выдается ошибка компиляции. Примеры ошибочных вызовов:

  suffix("a",("b","c")) // первый – корректный, у второго некорректна кардинальность
  suffix(1, "b") // неверный тип первого аргумента

Описание типов аргументов является обязательным.

Пользовательские структуры также могут быть аргументами:

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

Методы и последовательности

Если в контексте метода появляется последовательность, метод вычисляется поэлементно – отдельно вызывается для каждого элемента контекстной последовательности:

В этом случае результат вычислений для всех входных элементов группируется в единую выходную последовательность (в примере – (15,25,35,45,55)).

Результатом вычисления метода также может быть последовательность любой длины:

В данном примере выходная последовательность формируется "попарно": ((1,1),(2,2),(3,3),(4,4),(5,5)). Однако поскольку последовательности в Libretto плоские, эта запись эквивалентна (1,1,2,2,3,3,4,4,5,5).

Тип результата метода

Для метода может быть указан тип результата. Например, результат метода duplicate может быть определен так:

Компилятор старается вычислить тип результата самостоятельно. Однако в некоторых случаях указание типа результата обязательно, например, при определении рекурсивных методов:

Метод fact определяется рекурсивно, поэтому требуется явное описание типа результата.

Полиморфные определения методов

Полиморфное определение – это когда метод имеет несколько определений, каждое из которых обрабатывает данные определенного типа. Например,

Для контекстных значений компилятор старается подобрать метод с наиболее точным описанием типа. Например, для целого 115 подходят варианты с Int и Any (отсутствие явного описания типа эквивалентно Any). Но, поскольку Int более точно характеризует 115, то применяется первое правило, а не третье. Для вещественного 3.14 более точного описания, чем Any нет, поэтому применяется третье определение.

Оператор return

Оператор return позволяет завершить вычисление метода в любом месте и явно указать значение результата, например,

В этом примере делим одно число на другое нацело, проверяя делитель на ноль. Поскольку результатом может быть как число, так и строка, то тип результата получается объединенный (String | Int)!.

Для методов, содержащих return, указание типа результата обязательно.

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

В частности, последний пример может быть заменен на

Специальные методы

Libretto поддерживает специальные методы (как поэлементные, так и коллекционные), которые облегчают обработку последовательностей.

Коллекционные методы отличаются от поэлементных тем, что перед вызовом собирают в качестве контекста всю последовательность, с которой затем работают как с единым целым.

Сортировка

Коллекционный метод ^(<значение>) позволяет сортировать контекстную последовательность по значению–аргументу. Например,

Здесь сортировка проводится по строкам ('$' говорит о том, что сравниваются сами элементы последовательности).

Более интересный пример:

Здесь порядок наводится по фамилии, а для совпадающих фамилий – по имени.

Индексы

Специальный коллекционный метод [..] играет роль, аналогичную индексам в массивах:

  • Счет элементов последовательности ведется с 0.
  • Во втором случае делается вырезка от второго элемента до третьего
  • В третьем случае указан несуществующий индекс 4. Значение таких индексов равно ().
  • В четвертом случае: отрицательные индексы считаются с конца последовательности.

Фильтр

Фильтр ?[<условие>] пропускает только те элементы контекстной последовательности, которые удовлетворяют условию. Например,

Здесь фильтр пропускает значения не меньше, чем 5.

Фильтр структуры

Фильтр ?<имя структуры> пропускает только элементы последовательности, имеющие определенную структуру. Например, ?String пропускает только строковые значения. Пример:

Ловушка пустоты

Этот коллекционный метод срабатывает в случае, когда контекстная последовательность оказывается пустой. Синтаксис ловушки – exp1. ?(handler). path2. Если вычисление предыдущего пути path1 завершилось пустотой, то значением всей последовательности станет значение handler. В противном случае, ловушка игнорируется, и вычисления продолжаются как обычно.

Например,

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

Комбинирование коллекционных методов

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

Анонимные методы

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

Анонимные методы без аргументов

Анонимный метод без аргументов получится, если мы припишем знак процента к обычному блоку, например, #{x + 5}. Процент предотвращает немедленное вычисление (оценивание) блока и превращает его в портративный объект. Для того, чтобы вычислить (оценить) такой объект, применяется оператор !. Например,

Анонимный метод присвоен переменной fun, а затем оценен в контексте строки "a". Значение контекста берется с помощью $.

Контекст метода может быть типизирован:

По умолчанию контекст типизируется как Any, что может приводить к неожиданным результатам:

При сложении Libretto пытается свести экземпляр Any к строке, поэтому в этом примере вычисляется "10" + 5 == "105".

Замыкание

При создании анонимного метода, в нем фиксируются значения всех переменных, которые определены вне метода, но входят в него:

Анонимный метод #{x + 5} содержит вхождение переменной x. Несмотря на то, что у метода call имеется своя переменная x, используется то значение x, которое было зафиксировано в момент создания анонимного метода, то есть 10.

Если переменная x является изменяемой, то анонимный метод будет чувствительным к изменениям этой переменной:

Переменная x изменила значение после создания анонимного метода, но до его оценивания. Тем не менее, при оценивании использовалось актуальное значение переменной x.

Анонимные методы с аргументами

Можно определять анонимные методы с аргументами:

Здесь x – аргумент анонимного метода. Аргументам анонимного метода нужно в обязательном порядке задавать типы. При вызове анонимного метода, его актуальные параметры перечисляются после восклицательного знака в круглых скобках. Анонимные методы с N аргументами являются экземплярами структуры с именем LambdaN.

Анонимные методы можно передавать в другие методы в качестве параметров:

На анонимных методах с аргументами также работает замыкание:

Оператор return в анонимных методах

В анонимных методах можно использовать return. Однако важно помнить, что этот return будет относиться не к самому анонимному методу, а к тому методу, в котором этот анонимный метод определен:

Анонимные методы с return могут выполняться только внутри методов, в которых они определены.

Синтаксический сахар

Синтаксический сахар – альтернативный синтаксис представления конструкций языка, имеющий целью сделать эти конструкции проще и понятнее для восприятия.

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

В частности, в языке отсутствуют специальные конструкции для цикла while и средств управления исключениями try и finally. В Libretto это обычные методы. Благодаря синтаксическому сахару они могут использоваться в привычном для всех виде.

Двоеточие

В Libretto следующие форматы вызова метода fun(x,y,z) являются эквивалентными:

fun(x,y,z)
fun(x,y) : z
fun(x): y, z
fun: x, y, z

Например,

Очень эффективно сочетание синтаксического сахара "двоеточие" с синтаксическим сахаром "процент", действующим на анонимных методах.

Звездочка (коллекционный контекст)

Выразительное средство, позволяющее:

  • настраивать любые методы Libretto для работы с контекстом в конвейрном стиле
  • обрабатывать контекстные последовательности как единое целое, а не поэлементно.

В Libretto вызов метода fun(x, y) эквивалентен x.*fun(y).

Например, определим метод sum(seq:Int*), суммирующий элементы последовательности:

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

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

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

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

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

Инфиксный оператор

Любой двуместный метод может быть вызван в инфиксной форме, например,

В частности, с помощью этого синтаксического сахара можно записывать операции на множествах в "привычном" для математиков виде:

В пункте "Мэпы" мы использовали инфиксную запись для метода to.

Решетка

Определим

Данная программа может быть представлена в существенно более компактном и читабельном виде:

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

Сочетание синтаксического сахара "двоеточие" и "решетка" позволяет нам с помощью метода построить привычную всем конструкцию цикла while:

Сочетание "двоеточие+решетка" позволяет также компактно работать с транзакциями в базах данных:

Код, который должен быть исполнен в рамках транзакции, передается методу mem/w в виде анонимного метода.

08 Пакеты в Libretto

Структура пакета

Приложение – это код на Libretto, который представлен в виде набора пакетов. Каждый пакет состоит из

  • заголовка пакета (package <имя пакета>)
  • импорта других пакетов (use <имя пакета>)
  • определения структур данных и методов

Полное имя пакета: последовательность одного или более имен, разделенных косой чертой '/' и называемых доменами. Например, com/teacode/jet.

Пример пакета:

// заголовок пакета
package mypacks/symbols 
 
// импорт другого пакета
use libretto/text 
 
// определение структуры
struct Symbols(str: String*)
 
// определение метода
def String getSymbols = Symbols( this.text/chars )

Если имя пакета не указывать явно (как это сделано почти во всех примерах выше), то используется пакет по умолчанию с именем _default.

Расположение пакетов в файлах

Пакеты Libretto не зависят от файловой структуры программы:

  • в одном файле может быть определено несколько пакетов
  • один пакет может быть распределен по нескольким файлам

Например:

// Два пакета в одном файле

// Первый пакет

package mypack/a

def cent = 100


// Второй пакет в том же файле

package mypack/b 

use mypack/a 

def main = a/cent + 200


// Результат: 300

Импорт пакетов, использование методов и структур из других пакетов

При использовании метода и структуры из другого пакета нужно использовать префикс, который указывает на пакет.

Вариант 1. В качестве префикса можно использовать правый домен пакета:

package mypacks/people
 
struct Person(name:String!, surname:String!)
 
def String create(name:String!) = Person(name, this)
 
 
package main
 
use mypacks/people
 
def paul = 'Smith.people/create('Paul)
 
def johnsons(name:String!) = people/Person(name, 'Johnson)
 
def tom = 'Smith.people/create('Tom)
 
def main = johnsons('Paul).surname

// Результат: "Johnson"
Здесь вызов create выглядит как people/create, а использование Person как people/Person.

Вариант 2. В качестве префикса можно использовать псевдоним пакета:

package mypacks/people

struct Person(name:String!, familyName:String!)

def String create(name:String!) = Person(name, this)


package mine

use mypacks/people as p

def paul = 'Smith.p/create('Paul)

def johnsons(name:String!) = p/Person(name, 'Johnson)

def tom = 'Smith.p/create('Tom)

Здесь с помощью as определен псевдоним p пакета mypacks/people.

Рекомендация.  Правый домен имени пакета удобно использовать в качестве префикса, если количество букв в нем не превышает 3-4. В противном случае, лучше использовать псевдонимы.

Псевдонимы удобны и при корректировке неоднозначностей, например, в случае

use libretto/map
use com/teacode/map as map1

Импорт библиотек

Поскольку библиотеки являются пакетами Libretto, их использование аналогично использованию пакетов.