Libretto для веб-приложений |
Переменные и операторы присваивания
Контекст метода и ключевое слово this
Полиморфные определения методов
Комбинирование коллекционных методов
Анонимные методы без аргументов
Анонимные методы с аргументами
Оператор return в анонимных методах
Звездочка (коллекционный контекст)
Импорт пакетов, использование методов и структур из других пакетов
Это краткое описание Libretto – языка программирования, ориентированного на создание веб-приложений. Libretto устроен таким образом, что на нем можно разрабатывать все три основные компоненты веб-приложения:
При работе на Libretto нет нужды в использовании зоопарка разношерстных инструментов, которые обычно сопровождают веб-разработку.
Например, не нужны языки обмена данными (вроде JSON), поскольку на клиенте и сервере действуют одни и те же структуры – структуры Libretto.
Объекты баз данных также являются обычными структурами Libretto. Поэтому нет нужды в SQL и других внешних механизмах управления базами данных.
Необходимость в использовании Javascript также возникает крайне редко и далеко не во всех облачных приложениях.
Единое пространство программирования на сервере, клиенте и в базах данных, вкупе с исключительной компактностью языка позволяет повысить эффективность разработки и поддержки веб-приложений на Libretto в среднем в 4–5 раз.
Libretto ориентирован на создание веб-приложений. Однако на нем удобно писать и обычные ("автономные") программы. Libretto является полноценным языком со статической типизацией. Его эффективность примерно соответствует эффективности Java и Scala.
Когда Libretto–программа исполняется автономно, вычисления всегда начинаются с метода main
. Результатом выполнения программы является результат вычисления main
.
Пример автономной программы на Libretto:
В ней определен метод fact
, считающий факториал числа. Метод main
вычисляет факториал нескольких чисел.
В Libretto имеется два типа комментариев:
//...
, который начинается с двойной косой черты и завершается концом строки
/* ... */
Примеры комментариев:
2 + 2 // Это однострочный комментарий /* Это многострочный комментарий */
Чтобы запустить пример с факториалом на счет, нажмите кнопку "Run". Код можно откорректировать и запустить заново. Кнопка "Reset" восстанавливает первоначальный код.
Этот код можно также скопировать в редактор консоли Libretto IDE и там нажать кнопку "Run"
.
Имена в Libretto используются для обозначения переменных, методов, параметров, пакетов и т.д. Имеется два вида имен:
abc, a12, my_variable
`115`, `метод со сложным именем`
К базовым структурам данных относятся строки String
, целые числа Int
, вещественные числа Real
, а также структура Unit
с единственным элементом unit
.
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-документ, конвертированный с помощью библиотеки в веб-страницу.
и другие.
Могут быть положительными и отрицательными, и иметь любую длину.
1 -115 123798427349824729183471234123874291872934712934712938471298347
Базовые операции на целых числах:
'+'
(123 + 777 == 900)
'-'
(123 - 777 == -654)
'*'
(123 * 777 == 95571)
'div'
(115 div 3 == 1
)
'mod'
(115 mod 3 = 1
)
'-'
(-123
)
На целых числах также определены стандартные операции сравнения ==
, !=
, >
, <
, >=
, <=
.
В 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
.
Структуру 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 типы данных. Примеры типов данных:
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 являются примером пользовательских структур. Они определены в системной библиотеке 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"/>
.
Оператор блока – последовательность выражений в фигурных скобках, разделенных точкой с запятой:
Значением блока является значение его крайне правого выражения (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
.
Операторы присваивания +=
и .=
работают только на изменяемых переменных, имеющих кардинальность *
или +
.
Аналогичным образом операторы присваивания работают на мэпах, изменяя значения ключей:
Стандартный условный оператор, возвращающий значение в зависимости от значения условия.
Условие ложно, если оно равно пустой последовательности. В противном случае условие истинно.
Ветка else
может опускаться. В этом случае ее значение равно ()
:
Case
позволяет осуществлять разбор случаев:
Работают два вида проверок:
case 1 => ...
.
case i:Int => i + 100
. В этом случае переменная i
имеет тип Int!
.
Подчеркивание "_"
соответствует любому значению.
Оператор точки c.exp
вычисляет выражение exp
в контексте значений c
.
В этом примере контекст 1
подается методу add1
.
Если в контексте находится более одного значения, то оператор точки превращается в итератор, который последовательно "подает" значение за значением в качестве контекста:
Если в контексте находится пустая последовательность, то выражение после точки ни разу не выполняется:
Здесь выражение после точки содержит ошибку – деление на ноль. Однако поскольку контекст пустой, то до ошибки дело не доходит и вычисление останавливается на x
.
Цепочка применений оператора точки называется путем, например,
В пути реализуется "конвейерная" схема вычислений: результат вычисления предыдущего шага становится контекстом для следующего.
Для доступа к контексту внутри пути используется символ доллара $
:
На каждом шаге у $
свое значение:
Здесь первый $
равен 10, а второй – 15.
Оператор as
присваивает переменной текущее значение шага. Эта переменная действует до конца пути:
Используя as
можно увидеть, в каком порядке путь перебирает элементы последовательностей:
Видно, что в пути осуществляется перебор "в глубину".
Оператор index
присваивает переменной индекс текущего элемента последовательности шага:
Нумерация элементов начинается от 0
.
В следующем примере делается выборка четных элементов последовательности.
Оператор index
локален: для каждого контекстного значения шага нумерация начинается заново:
Пустая последовательность играет важную роль для вычислений в пути. Если некоторый шаг принимает значение ()
, то вычисление дальше не идет:
В данном примере шаг even
становится пустым для нечетных чисел в контексте. Поэтому для них вычисление квадрата не делается.
Часто удобно интерпретировать пустоту на некотором шаге как неудачу (failure) в вычислениях для данного значения контекста. Пустоту шагов можно обрабатывать. Для этого существует специальная "ловушка пустоты" ?(..)
.
Для блока в пути есть две возможности:
В первом случае блок меняет контекстное значение, во втором – не влияет на него:
В этом примере: для res1
блок {$ + 2}
является отдельным шагом, его значение является значением всего пути. Для res2
блок является дополнением к 5
(между ними нет точки). Здесь блок не влияет на значение всего выражения. Блок второго вида называется вспомогательным.
Мы рекомендуем активно пользоваться вспомогательными блоками. Например, они позволяют вставлять отладочный код без боязни испортить результат вычислений:
Здесь блок никак не влияет на результат вычисления.
В качестве еще одного примера использования вспомогательных блоков реализуем глобальный счетчик. Счетчик организуем в базе данных, работающей в оперативной памяти (управление этой базой реализовано в библиотеке libretto/mem
). Для работы со счетчиком введем структуру Counter
"оборачивающую" мэп базы данных. Метод get
возвращает текущее значение счетчика, хранящегося в мэпе. Если значение счетчика отсутствует, то он инициализируется. Метод add
увеличивает значение счетчика на 1
.
Предположим, что нам нужен метод, возвращающий удвоенное значение счетчика, и при этом увеличивающий его:
Определены два метода, решающие эту задачу – oper1
и oper2
. Первый метод реализован в стандартном стиле, а второй использует вспомогательный блок. Разница видна невооруженному глазу.
Такой же блок используется при инициализации счетчика в методе getcount
. Инструменты для работы с базами данных и ловушка пустоты .?(...)
описаны ниже.
Определение метода начинается с ключевого слова def
, после которого идет заголовок метода, а затем его тело:
def hello = "Привет!"Заголовок состоит из имени метода
hello
. Строка "Привет!"
– тело метода. Заголовок и тело метода разделяются знаком равенства.
Метод можно вычислить:
Методы, как и любые другие выражения 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
позволяет завершить вычисление метода в любом месте и явно указать значение результата, например,
В этом примере делим одно число на другое нацело, проверяя делитель на ноль. Поскольку результатом может быть как число, так и строка, то тип результата получается объединенный (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
могут выполняться только внутри методов, в которых они определены.
Синтаксический сахар – альтернативный синтаксис представления конструкций языка, имеющий целью сделать эти конструкции проще и понятнее для восприятия.
В 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 вызов метода fun(x, y)
эквивалентен x.*fun(y)
.
Например, определим метод sum(seq:Int*)
, суммирующий элементы последовательности:
Метод одноаргументный, но использование звездочки позволяет использовать его как нульместный метод, суммирующий контекстные последовательности.
Важно то, что на вход методу подается все коллекция сразу, а не ее элементы один за другим. Например, в третьем случае на вход подается пустая последовательность, что невозможно для обычных контекстных методов.
В качестве другого примера, реализуем операции пересечения, объединения и разности множеств. В роли множеств будут выступать последовательности.
Реализация не самая эффективная, однако интересная с точки зрения демонстрации конвейерных инструментов работы с последовательностями. В данном примере с помощью синтаксического сахара "звездочка":
Любой двуместный метод может быть вызван в инфиксной форме, например,
В частности, с помощью этого синтаксического сахара можно записывать операции на множествах в "привычном" для математиков виде:
В пункте "Мэпы" мы использовали инфиксную запись для метода to
.
Определим
Данная программа может быть представлена в существенно более компактном и читабельном виде:
Используя #
в аргументе метода, мы подсказываем Libretto, что этот аргумент не должен вычисляться, а должен интерпретироваться как анонимный метод.
Сочетание синтаксического сахара "двоеточие" и "решетка" позволяет нам с помощью метода построить привычную всем конструкцию цикла while
:
Сочетание "двоеточие+решетка" позволяет также компактно работать с транзакциями в базах данных:
Код, который должен быть исполнен в рамках транзакции, передается методу mem/w
в виде анонимного метода.
Приложение – это код на 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, их использование аналогично использованию пакетов.