Sunday, February 5, 2017

Flamberg: tutorial

Первое знакомство

Hello world.

"Hello world"

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

Можно использовать Flamberg в качестве калькуллятора
2*2
Все, что возвращено из программы будет напечатано в консоль, если это конвертируется в текст.

То же, но с форматированием строки
"result={2*2}"
В строку, заключенную в кавычки можно вставлять выражения в фигурных скобках. Они вычислятся, сконвертируются в строку и вклеются в нужные места строки.

Фламберг поддерживает однострочные комментарии, начинающиеся с ';' и следующие до конца строки.
; this is a comment
"Hi" ; this is also a comment

Объявим несколько именованных значений и используем их в вычислениях.
a = 2016
n = "Andrey"
"Hello {n}, happy {a + 1}"
Обратите внимание, a и n не переменные - это имена, связанные с числом 2016 и строкой "Andrey".
Если вы хотите изменять значение, свяжите имя не с числом, а с числовой переменной, это делается так:
a = var 2016
a++

Функции

Объявим функцию и вызовем ее:

square = : x
    x * x

echo square(12)
echo square(12.5)
Конструкция ": x x*x" декларирует анонимную фунцию, а "square =" дает ей имя.
Объявление функции начинается с символа ':' за которым через пробел следуют имена параметров и далее с отступом записываются стейтменты, составляющие тело функции.

В данном примере у параметра функции 'x' не указан тип. Это традиционно для фламберга. Если тип не указан, функция становится шаблоном, и тогда ее можно вызывать с любым типом параметров. Для каждого вызова с уникальным типом параметров компилятор сделает свою версию функции. В данном примере будут сгенерированы функции int->int и float->float.

Рассмотрим еще несколько вариантов синтаксиса для вызова функций:
echo(add(42 15))
echo(42+15)
echo 42 + 15
echo
    add 42 15
echo
    add
        42
        15
Все эти выражения эквивалентны.
  • Первая строка демонстрирует традиционный синтаксис (единственное отличие - не нужна запятая между параметрами.
  • Вторая строка показывает, что все префиксные, инфиксные и суффиксные операторы - суть просто вызовы функций с предопределенными именами.
  • Третья строка показывает, что любую функцию можно вызывать как стейтмент без скобок. Если в одной строке содержится несколько выражений, разделенных пробелами, это вызов функции, заданной первым выражением, а все остальные выражения становятся ее параметрами.
  • Четвертая строка показывает, что параметры можно записывать с отступом, в этом случае они сами могут быть стейтментами.
  • Шестая строка показывает, что запись с отступами может иметь сколько угодно вложений.

Просуммируем содержимое массива (в императивном стиле):
data = [1 2 44 2 12]
s = var 0
for data: i
    s += i
  • Выражение "[1 2 44 2 12]" эквивалентно вызову функции sequence.
  • Функция sequence принимает переменное число однотипных параметров и возвращает содержащую их iterable-последовательность. Поскольку параметры функции - константы, она развернется во время компиляции и превратится в проинициализированный сегмент данных.
  • Выражение "s=var 0" это вызов функции var с параметром 0. Эта функция вернет особый объект - "переменную", имеющую начальное значение 0 и тип int - тип своего инициализатора. С объектом-переменной можно проделывать две операции - читать текущее значение и задавать новое значение.
  • Выражение "for data : i" - это уже знакомый нам вызов функции. Функция for принимает два параметра - iterable-последовательность и функцию, которую нужно вызвать для каждого элемента последовательности.
  • Таким образом "data" - первый параметр функции "for", а ": i" - второй парметр.
  • Выражение ": i" - это декларация анонимной функции с параметром i, тело которой записывается на следующих строках с отступом.
  • Выражение "s += i" - это вызов функции setAdd(s i), которая требует, чтобы первым параметром была переменная, а вторым - значение совместимого с переменной типа.
  • "i" - имя параметра, в который функция for будет передавать каждый элемент последовательности
  • "s" - имя переменной из внешнего лексического контекста.

Этот короткий пример показывает несколько особенностей фламберга
  • Все конструкции языка реализуются функциями, в которые передаются другие функции.
  • for, while, if, switch... - это просто функции стандартной библиотеки (и вы можете написать свои, если захотите).
  • Вы можете переопределить любые функции, например sequence, for, var и т.д. В языке нет ничего "встроенного".
  • Само понятие переменной реализуется библиотечной функцией var. Существуют особые функции ref, weak, shared и др. которые позволяют строить сложные ссылочные модели.
  • Существуют функции с переменным числом параметров.
  • Все функции замкнуты на все имена из окружающих лексических контекстов.


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

Напишем функцию, вычисляющую максмальное значение
max = : a b
    if a > b :a :b

echo
  max 123 444
Функция if принимает три параметра - условие и две функции. Она вызывает одну из переданных функций в зависимости от условия и возвращает ее результат.
Выражение ":a" - это анонимная функция без параметров, возвращающая а из внешнего контекста.

Функцию if можно вызвать разными способами
if
   a > b
   :a
   :b

if a > b:
   a
else:
   b

if
    a > b
then:
    a
else:
    b
Последние два варианта используют еще один вариант вызова функции: именованные параметры.
Общий синтаксис стейтмента вызова функций:
выражение_задающее_функцию выражение_параметр1 параметр2 выражение_параметрN
     стейтмент-параметрN+1
     стейтмент-параметрN+2
     стейтмент-параметрM
имя_параметра стейтмент-параметрM+1
имя_параметра стейтмент-параметрM+2

Замыкания

Рассмотрим вот такой код:

Person = : name age
    :"{name} of {age}"

me = Person "Andrey" 44
wife = Person "Katya" 44-7

echo me()
echo wife()

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

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

Атомы

Фламберг имеет особый элементарный тип данных - атом.

Для атомов определены всего две операции.

  • декларация константы (записывается как точка, за которой следует идентификатор)
  • проверка на равенство a==b
Пример:
a = .myAtom
b = .anotherAtom

if a == .someDifferentAtom :"matched" :"didn't match"

В рантайме атомы - это просто уникальные целые числа.

Диспетчеры

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

colors = #
   red   0xff0000
   green 0x00ff00
   blue  0x0000ff

echo colors.green

Colors - это функция-диспетчер принимающая атом, и для трех перечисленных атомов возвращающая целые числа. Поведение диспетчера для прочих атомов не определено, поэтому такую функцию можно вызывать только с параметром - константой.
Обратите внимание, что в ключах диспетчера атомы записываются без точки.
А при вызове диспетчера "colors.green" нет скобок. Вообще функции с единственным парметром - атомом можно вызывать без скобок.

Классы, интерфейсы, структуры

Их нет. Они строятся из диспетчеров и замыканий:

Person = : name age
    #
        name name
        age  age

me = Person "Andrey" 44
echo me.name
Функция принимает два параметра и возвращает диспетчер, который по атому name открывает доступ к локальному имени name и аналогично для атома и локального имени age.

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

Экпорт локальных имен под одноименными атомами настолько распространенная практика, что Фалмберг имеет для этого простой синтаксис: "##"
Запишем этот же пример проще:
Person = : name age ##    ; описали структуру
me = Person "Andrey" 44 ; создали экземпляр
echo me.name  ; обратились к полям
Добавим к нашей структуре пару методов, чтобы получить полноценный класс.
Person = : name age ##
    getDescription  "Person {name} of age {age}"
    isTeenager      age > 12 && age < 20
    isOlderThan: p
         age > p.age

daughter = Person "Polina" 13
son =      Person "Anton" 6

greeting = : p
   echo
      if p.isTeenager :"yo!" :"hello!"
      p.name

for [son daughter]: p
    greeting p
    echo p.age

oldest = if son.isOlderThan(daughter) :son :daughter
echo "the eldest is {oldest.getDescription}"
  • В строках с 1 по 5 объявляется класс Person функция Person. Ее параметры являются параметрами конструктора, и полями объекта (те из них, которые использованы в методах).
  • Ее результат диспетчерская функция реализация интерфейса.
  • Атомы getDescription, isTeenager, isOlderThan идентифицируют методы.
  • Кроме того два атома name и age генерируются конструкцией "##".
  • Интересно, что первые два атома - прямо возвращают строку и логический результат, а последний возвращает функцию с одним параметром, которую еще нужно вызвать.
  • В строке 19 присутствет такой вызов "son.isOlderThan(daughter)". Здесь вначале вызывается диспетчер с атомом ".isOlderThan" затем возвращенная им функция вызывается с параметром "daughter".
  • В строке 10 определяется функция greeting. Как всегда это шаблон. Она подходит для любого диспетчера, у которого есть isTeenager -> bool и name->string.

Многострочные текстовые литералы

echo"
      Превед, my name is Медвед
      I'm pretty sure we have met.
Параметром функции echo является многострочный текст.
  • Он начинается с кавычки, стоящей в конце строки.
  • Он идет в следующих строках с отступом.
  • Он может содержать произвольные символы.
  • Символы перехода на следующую строку становятся частью текстовой строки.
  • Начальный отступ - не включается в строку. Но все отступы, глубже начального - включаются.
  • Как и в случае однострочного литерала, строка может содержать выражения в фигурных скобках. Если элементы в фигурных скобках не константы, это уже не текстовый литерал, а вызов функции concatenate со всеми константными и не константными фрагментами.
n = "Andrey"
t = "
   Hello {n},
   How are you?
echo t

Вызов цепочки функций

Очень часто бывает нужно вызвать функцию, которую возвращает другая функция.

Однострочный синтаксис:
function(parameters of first call)(parameters of second call)
Многострочный синтаксис:
function
   parameters
   of
   first
   call
..
   parameters
   of
   second
   call

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

Разберем примеры из предыдущего поста

mySql.connect dbName user password
..query "
   select name, id
   from users
   limit 100, {request.page}
..map: u "
   <li id="u_{u.id}">
      {u.name}
   </li>
..echo
  • Вызывается функция "mySql" с атомом ".connect".
  • Она возвращает функцию подключения к базе данных, кототая тут же вызывается с тремя параметрами dbName user password.
  • Она возвращает замкнутый диспетчер объект, установленного соединения.
  • У этого объекта вызывается метод ".query" с параметром - многострочным текстовым выражением. Это SQL-запрос, Обратите внимание, в этот запрос вставляется параметр request.page.
  • Метод query возвращает iterable-последовательность записей.
  • У всех iterable-последовательностей есть метод map, который принимает функцию с одним параметром и возвращает новую последовательность, состоящую из результатов этой функции, поэлементно примененной к исходной последовательности.
  • Анонимная функция принимает структуру в виде параметра "n" и возвращает строки содержащие поля из "n".
  • И наконец у последовательности вызвается метод echo, который выводит ее в консоль.

Надо сказать, что map и echo - не методы, а обычные функции. Просто Фламберг позволяет вызывать функции как методы.

Этот же пример можно записать в одну строку (но зачем?):
echo(map(mySql.connect(dbName user password).query("select name, id from users limit 100, {request.page}") (: u "<li id={QUOT}u_{u.id}{QUOT}>{CR}{TAB}{u.name}{CR}</li>")))

Остальные примеры с комментариями
+unsafe                   ; импорт модуля. О них - в следующий раз
ptr(0x12345).byte ^= 0x80 ; "^= " исключающее или
; создадим объекты
root = Group 10 10 []    ; "[]" - многострочный вариант вызова функции sequence
   Circle 100 100 40 color.red
   Group 100 100 []
      Rectangle 5 5 20 40 color.black
      Circle 20 20 20 color.red
; fizz-buzz
for 0..101: i ; инфиксный оператор ".." - это вызов функции range(0 101)
   echo
      if i%3:               ; % - остаток от деления
         if i%5 :i.toString :"buzz"
      else:
         if i%5 :"fizz"     :"fizzbuzz"

inputFile "text.txt"        ; открыть файл на чтение
..readLines                 ; получить его iterable-последовательность строк
..map: line                 ; отпарсить каждую строку...
    name = line.getTill "|"
    age =  line.getInt
    ##                      ; ... и преобразовать ее в структуры name-age
..filter: r  r.age > 16     ; отфильтровать последовательность по правилу
..orderBy: r r.name         ; отсортировать ее по полю
..map: r "                  ; преобразовать структуры в строки текста
   <tr>
      <th>{r.name}</th>
      <td>{r.age}</td>
   <tr>
..echo                      ; вывести последовательность строк в консоль

No comments:

Post a Comment