[Из песочницы] Самый выразительный. Краткое пособие по языку Red

Привет всем!

Сегодня я хотел бы рассказать о языке программирования Red. Язык Red является непосредственным преемником более раннего языка REBOL. Оба они малоизвестны в русскоязычном сообществе, что само по себе странно, т.к. указанные языки представляют интерес как с точки зрения оригинального взгляда на то, каким должно быть программирование, так и тем, что отличаются потрясающей выразительностью, позволяя решать сложные задачи простыми способами.

Данная статья призвана исправить сложившуюся ситуацию, являясь первым пособием по основам языка Red на русском языке.

1iyesm0wit5ldk-bq4nb9cit4q8.png

О языке Red
В 1997 году Карлом Сассенратом, бывшим основным разработчиком AmigaOS, был предложен язык REBOL (http://www.rebol.com/). Однако разработка REBOL прекратилась к 2010 году, и в качестве его преемника в 2011 году Ненадом Ракоцевичем был анонсирован язык Red (http://www.red-lang.org/), наследуя синтаксис родоначальника и призванный его превзойти.

Одним из главных преимуществ Red/REBOL является его исключительная выразительность, позволяющая реализовать заданную функциональность минимальным количеством кода. Следующий график иллюстрирует результат исследования данного показателя для разных языков программирования:

aaa6ogefzbsytjckgsvtwgncvdw.png
Сравнение выразительности языков программирования (источник)

Из графика видно, что REBOL является самым выразительным языком общего назначения (лидирующие Augeas и Puppet — языки узких предметных областей). Red в ряде случаев является даже более выразительным, чем REBOL.

Другие ключевые отличия и достоинства языка Red:

  • Язык полного стека — от низкоуровневого программирования до написания скриптов.
  • Создание мультиплатформенных приложений.
  • Поддержка кросс-платформенного нативного GUI.
  • Легкое создание DSL.
  • Функциональное, императивное, реактивное программирование.
  • Гомоиконность (способность программы обрабатывать свой код в качестве данных).
  • Развитая поддержка макросов.
  • Большое число встроенных типов.
  • Исключительная простота установки, не требующая инсталляции и настройки.


На текущий момент Red активно развивается, поэтому часть возможностей еще только готовится к реализации. Среди таких возможностей, не реализованных на момент написания данной статьи: поддержка Android и iOS GUI, поддержка в полном объеме операций ввода/вывода, поддержка модульной компиляции, поддержка параллелизма.

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

Установка программы


Установка Red под платформу Windows:

  1. Скачайте исполняемый файл, расположенный по адресу (файл весит всего лишь около 1МВ).
  2. Разместите скачанный файл в выбранную папку.
  3. Запустите скачанный файл. Выполнится сборка GUI-консоли, которая займет некоторое время (сборка осуществляется лишь при первом запуске).


После того, как сборка выполнится, откроется консоль — Red готов к работе.

Hello World!


Консоль вызывается всякий раз, когда исполняемый файл Red запускается без аргументов. Консоль дает возможность работать с кодом Red в режиме интерпретации.
Для создания первой программы, выводящей текст «Hello world!», введите и выполните в консоли следующий код:

print "Hello World!"


Для создания программы с нативным GUI:

view [text "Hello World!"]


Или немного более сложный вариант:

view [name: field button "Hello world!" [name/text: "Hello world!"]]


Создание исполнимого файла


Для компиляции кода Red в исполнимый файл выполните следующие шаги:

  1. Введите в текстовом редакторе следующий код (для компиляции, в отличие от интерпретации, код должен обязательно содержать заголовок):
    Red []
    print "Hello World!"
  2. Сохраните код в файл hello.red в папке, где расположен Red.
  3. В терминале выполните команду (если имя вашего файла компилятора отличается от red, поменяйте его на корректное): red -c hello.red


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

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

Red [Needs: 'View]
view [text "Hello World!"]


Общие сведения о программах Red

Диалекты


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

  • Red/Core — основа языка Red.
  • Red/System — диалект для программирования на системном уровне.
  • Red/View — набор диалектов для визуализации (VID и Draw).
  • Red/Parse — диалект, используемый для парсинга.


Далее в статье пойдет речь об основе языка — Red/Core.

Файлы


Файл, содержащий код Red, должен иметь расширение .red и кодировку UTF-8.

Заголовки


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

Для программ Red заголовок в общем случае имеет вид:

Red [block]


Важно! Несмотря на то, что Red нечувствителен к регистру, слово «Red» в заголовке обязательно должно писаться в точности, как показано в примере — с заглавной буквы.

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

Red []


Стандартный заголовок:

Red [
    Title:  "Hello World!" ;-- название программы
    Version: 1.1.1         ;-- версия программы
    Date:    7-Nov-2017    ;-- дата последней модификации
    File:   %Hello.red     ;-- название файла
    Author: "John Smith"   ;-- имя автора
    Needs:  'View          ;-- зависимости
]


Комментарии


Программы Red могут содержать комментарии.
Для комментария, состоящего из одной строки, используется точка с запятой:

max: 10   ;-- максимальное значение параметра


Для комментария, состоящего из нескольких строк используются фигурные скобки {}. Для того, чтобы быть точно уверенным, что такие комментарии будут восприняты Red как комментарии, а не как код, перед ними следует указать ключевое слово comment:

{
    Это многострочный
    комментарий
}

comment {
    Это многострочный
    комментарий
}


Основы синтаксиса

Блоки


Программа, написанная на Red, состоит из блоков, комбинирующих значения (values) и слова (words). Блоки заключаются в квадратные скобки []. Значения и слова в составе блоков всегда разделяются одним или несколькими пробелами — это важно для правильной интерпретации кода.

Блоки используются для кода, списков, массивов и других последовательностей, представляя собой разновидность серий (series).

Примеры блоков:

[white yellow orange red green blue black]

["Spielberg" "Back to the Future" 1:56:20 MCA]

[
    "Elton John"  6894  0:55:68
    "Celine Dion" 68861 0:61:35
    "Pink Floyd"  46001 0:50:12
]

loop 12 [print "Hello world!"]


Значения


Каждое значение в Red имеет свой тип. По умолчанию, наименования типов оканчиваются восклицательным знаком !.

Наиболее часто используемые типы:

Тип Описание Примеры
word! Слово программы, рассматриваемое в качестве значения и не требующее выполнения (литерал). 'print
block! Блок слов программы. [red green blue]
integer! Целое число. 1234
-324
float! Дробное число. 1.234
3,14
1,37E5
binary! Байтовая строка произвольной длины. #{ab12345c}
string! Строка.
Если строка содержит переносы, кавычки, табуляцию, то она заключается в фигурные скобки {}.
"Hello world!"
{Строка, состоящая из
нескольких строк}
char! Символ. #"C"
logic! Булевское значение. true
false
time! Время. 12:05:34
9:45.25
date! Дата. 07-November-2017
07/11/17
tuple! Номер версии, значение цвета RGB, сетевой адрес.
Должен содержать как минимум три значения, разделенных точками.
255.255.0.0
235.17.22
pair! Пара значений для координат, размеров. 100x200
file! Название файла, включая путь. %red.exe
%images/logo.jpg


Слова


Слова в Red должны удовлетворять следующим требованиям:

  • Могут включать в себя буквы, цифры и следующие символы: ? ! . ' + - * & | = _ ~
  • Не могут содержать символы: @ # $ % ^ ,
  • Не могут начинаться с цифры или быть составлены таким образом, что могут быть интерпретированы как числа.


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

Формат Комментарий
word Возврат значения, которое содержит слово. Если слово содержит функцию, то функция выполняется.
word: Присвоение слову нового значения.
:word Получение значения слова без его выполнения.
'word Рассмотрение слова как символа, без его выполнения. Слово само рассматривается в качестве значения.


Присвоение значений (создание переменных)


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

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

age: 33
birthday: 11-June-1984
town: "Moscow"

cars: ["Renault" "Peugeot" "Skoda"]
code: [if age > 32 [print town]]
output: function [item] [print item]


Присвоить значение слову/словам можно также с помощью функции set:

set 'test 123
print test
; --> 123

set [a b] 123
print [a b]
; --> 123 123


Стоит обратить внимание, что при присваивании значения слову перед этим словом стоит одиночная кавычка, указывающая, что это слово — литерал, и оно не должно выполняться (имеет тип word!). Но слова внутри блока не требуют кавычек, т.к. содержимое блока не выполняется без явного указания.

Получение значений


Двоеточие перед словом (:) используется для получения значения слова без его выполнения.

Сказанное можно проиллюстрировать следующим примером:

печать: :print
печать "test"
; --> test


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

Получить значение слова можно также с помощью функции get:

печать: get 'print
печать "test"
; --> test


Литералы


Литералы представляют собой слова Red, рассматриваемые в качестве значений, и имеют тип word!

Литералы можно задать двумя способами: указанием одиночной кавычки перед словом (') или размещением слова внутри блока.

word: 'this
print word
; --> this

word: first [this and that]
print word
; --> this


Рассмотренные выше функции set и get требуют литералы в качестве своих аргументов.

Пути


Пути представляют собой набор значений, разделяемых прямым слешем (/). Значения в составе пути называются уточнениями (refinements). Пути используются для навигации и поиска.

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

Russia/MO/Podolsk/size Выбор значения из блока.
names/12 Выбор значения из строки по заданной позиции.
account/balance Доступ к функции в составе объекта.
sort/skip Уточнение действия функции.


Выполнение выражений


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

print 5 + 4 * 3
; --> 27
print absolute -3 + 5
; --> 2

print 5 + (4 * 3)
; --> 17
print (absolute -3) + 5
; --> 8


Для выполнения блока используется функция do. Особенность функции do состоит в том, что она возвращает только последнее вычисленное значение:

do [1 + 2]
; --> 3

do [
    1 + 2
    3 + 4
]
; --> 7


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

reduce [
    1 + 2
    3 + 4
]
; --> [3 7]


Функции управления выполнением

Условные операторы


Функция if имеет два аргумента — логическое выражение и блок кода. Если логическое выражение имеет значение true, то блок кода выполняется. Если логическое выражение имеет значения false или none, то блок не выполняется, и функция возвращает none.

a: -2
if a < 0 [print "a - отрицательное число"]
; --> a - отрицательное число

print if a < 0 ["a - отрицательное число"]
; --> a - отрицательное число


Функция either похожа на функцию if с тем отличием, что имеет дополнительный третий аргумент — блок кода, который выполняется в случае, если логическое выражение не соблюдается (т.е. имеет значение false или none).

b: 3
either b < 0 [
    print "b - отрицательное число"
][
    print "b – не отрицательное число"
]
; --> b – не отрицательное число


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

size: 40
if any [size < 20 size > 80] [
    print "Значение вне рамок диапазона"
]
; --> Значение вне рамок диапазона


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

size: 40
if all [size > 20 size < 80] [print "Значение в рамках диапазона"]
; --> Значение в рамках диапазона


Условные циклы


Функция while имеет два аргумента в виде блоков кода, циклически выполняя их до тех пор, пока первый блок возвращает значение true. Если первый блок возвращает значение false или none, второй блок не выполняется и осуществляется выход из цикла.

a: 1
while [a < 3][
    print a
    a: a + 1
]
; --> 1
; --> 2


Функция until имеет один аргумент в виде блока кода, циклически выполняя его до тех пор, пока блок не вернет значение true. Блок кода выполняется, как минимум один раз.

a: 1
until [
    print a
    a: a + 1
    a = 3
]
; --> 1
; --> 2


Циклы


Функция loop циклически выполняет блок кода заданное число раз, возвращая последнее вычисленное значение.

i: 0
print loop 20 [i: i + 20]
; --> 400


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

i: 0
print repeat k 10 [i: i + k]
; --> 55


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

colors: [red green blue]
foreach color colors [print color]
; --> red
; --> green
; --> blue


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

colors: [red green blue]
forall colors [print first colors]
; --> red
; --> green
; --> blue


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

a: 1
forever [
    a: a * a + 1
    if a > 10 [print a break]
]
; --> 26


Прерывание цикла


Функция continue позволяет прервать выполнение текущей итерации цикла и перейти к следующей итерации.

repeat count 5 [
    if count = 3 [continue]
    print ["Итерация" count]
]
; --> Итерация 1
; --> Итерация 2
; --> Итерация 4
; --> Итерация 5


Функция break позволяет выйти из цикла.

repeat count 5 [
    if count = 3 [break]
    print ["Итерация" count]
]
; --> Итерация 1
; --> Итерация 2


Выборочное выполнение


Функция switch выполняет первый из блоков, соотнесенный с заданным значением. Функция возвращает значение блока, который был выполнен, или none в обратном случае. Функция также позволяет использовать множество значений для сопоставления.

switch 1 [
    0 [print "Ноль"]
    1 [print "Единица"]
]
; --> Единица

num: 7
switch num [
    0 2 4 6 8 [print "Четное число"]
    1 3 5 7 9 [print "Нечетное число"]
]
; --> Нечетное число


Функция case выполняет первый из блоков, для которого выполняется соотнесенное с ним условие. Функция возвращает значение блока, который был выполнен, или none в обратном случае.

a: 2
case [
    a = 0 [print "Ноль"]
    a < 0 [print "Отрицательное число"]
    a > 0 [print "Положительное число"]
]
; --> Положительное число


Команды ввода/вывода


Функция print позволяет вывести на экран заданное значение. Если значение является блоком, то перед выводом к нему негласно применяется функция reduce.

print 1234
; --> 1234

print [2 + 3 6 / 2]
; --> 5 3


Функция prin практически идентична print за тем исключением, что после вывода значения не выполняется перенос на новую строку. Увидеть различие в работе можно при выводе в терминал, но не в консоль.

prin "Александр "
prin "Невский"
; --> Александр Невский


Функция probe позволяет вывести на экран заданное значение, которое перед выводом преобразуется в строку кода Red.

colors: [red green blue]
probe colors
; --> [red green blue]


Функция input позволяет осуществить ввод значения.

prin "Введите ваше имя: "
name: input
print ["Здравствуйте," name]
; --> Здравствуйте, Антон


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

qwnt8yyfzaaz1jjwfgnnmuot-7i.jpeg

  • голова (head) — первая позиция в серии.
  • хвост (tail) — позиция, идущая вслед за последним элементом серии.
  • позиция — текущая позиция.


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

colors: [red green blue]
print first colors
; --> red


Извлечение значений


Для извлечения значений из серии относительно текущей позиции служат следующие порядковые функции:

  • first — значение текущей позиции.
  • second — значение второй позиции относительно текущей.
  • third — значение третьей позиции относительно текущей.
  • fourth — значение четвертой позиции относительно текущей.
  • fifth — значение пятой позиции относительно текущей.
  • last — значение последней позиции в серии.


colors: [red green blue yellow cyan black]

print first colors
; --> red
print third colors
; --> blue
print fifth colors
; --> cyan
print last colors
; --> black


Для извлечения значения по номеру позиции можно использовать пути или функцию pick.

print colors/3
; --> blue
print pick colors 3
; --> blue


Изменение позиции


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

values: [1 2 3]

print next values
; --> 2 3
print first values
; --> 1

values: next values
print first values
; --> 2


Для смещение текущей позиции на одну позицию назад служит функция back.

values: back values
; --> [1 2 3]
print first values
; --> 1


Для смещения сразу на несколько позиций служит функция skip. В случае, если смещение имеет положительное значение, то осуществляется смещение вперед, а если отрицательное — назад.

values: [1 2 3]

probe values: skip values 2
; --> [3]
probe values: skip values -2
; --> [1 2 3]


Для смещения непосредственно в голову или на хвост серии служат функции head и tail соответственно (стоит напомнить, что хвостом серии служит позиция, идущая вслед за последним элементом серии).

values: [1 2 3]

probe values: tail values
; --> []
probe values: head values
; --> [1 2 3]


Вставка и добавление значений в серии


Для вставки одного или нескольких значений в серию используется функция insert. Значение вставляется на место текущей позиции, при этом текущая позиция не изменяется.

colors: [green blue]

insert colors 'red
probe colors
; --> [red green blue]


С помощью функции insert можно осуществлять вставку в произвольное место серии и вставку нескольких значений.

colors: [green blue]

insert next colors 'red
probe colors
; --> [green red blue]

insert colors [silver black]
probe colors
; --> [silver black green red blue]


Работа функции append схожа с работой функции insert с тем отличием, что новое значение или значения всегда добавляются в конец серии.

colors: [green blue]

append colors 'red
probe colors
; --> [green blue red]


Удаление значений из серий


Для удаления одного или нескольких значений серии используется функция remove. Удаляется значение, на которое указывает текущая позиция.

colors: [red green blue yellow cyan black]

remove colors
probe colors
; --> [green blue yellow cyan black]


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

colors: [red green blue yellow cyan black]

clear skip colors 3
probe colors
; --> [red green blue]

clear colors
probe colors
; --> []


Изменение значений серий


Для изменения одного или нескольких значений серии используется функция change:

colors: [red green blue]

change next colors 'yellow
probe colors
; --> [red yellow blue]


Работа функции poke схожа с работой функции change с тем отличием, что она позволяет явно указать номер позиции относительно текущей, значение которой будет изменено.

colors: [red green blue]

poke colors 2 'yellow
probe colors
; --> [red yellow blue]


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

colors: [red green blue green]
replace colors 'green 2
; --> [red 2 blue green]


Создание и копирование серий


Функция copy позволяет создать новую серию путем копирования существующей. Копирование также важно в случае использования функций, модифицирующих исходную серию, и при желании сохранить исходную серию неизменной.

str: copy "Копируемая строка"
new-str: copy str

blk: copy [1 2 3 4]

str2: uppercase copy "Копируемая строка"


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

colors: [red green blue yellow]

sub-colors: copy/part next colors 2
probe sub-colors
; --> [green blue]

probe copy/part colors next colors
; --> [red]
probe copy/part colors back tail colors
; --> [red green blue]


Поиск в сериях


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

a: [1 2 3 4 5]
probe find a 2
; --> [2 3 4 5]
probe find a 7
; --> none


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

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

colors: [red green blue yellow]
print select colors 'green
; --> blue

colors: [
    1 red
    2 green
    3 blue
    4 yellow
]
print select colors 2
; --> green


Сортировка серий


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

names: [Иван Андрей Максим Юрий Вячеслав Дмитрий]
probe sort names
; --> [Андрей Вячеслав Дмитрий Иван Максим Юрий]

print sort [22.8 18 132 57 12 64.9 85]
; --> 12 18 22.8 57 64.9 85 132

print sort "валидация"
; --> аавдиилця


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

probe sort copy names


По умолчанию сортировка осуществляется по возрастанию. Для изменения направления сортировки используется уточнение /reverse:

print sort/reverse [22.8 18 132 57 12 64.9 85]
; --> 132 85 64.9 57 22.8 18 12


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

colors: [
    3 red
    4 green
    2 blue
    1 yellow
]
probe sort/skip colors 2
; --> [
; -->     1 yellow
; -->     2 blue
; -->     3 red
; -->     4 green
; --> ]


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

Работа с блоками
Блоки могут содержать в своем составе другие блоки. Вне зависимости от того, сколько значений содержит вложенный блок, он рассматривается как одно значение для внешнего блока. Это определяет правила обращения к значениям блоков для их извлечения.

values: [
    123 ["one" "two"]
    %file1.red ["test1" ["test2" %file2.txt]]
]
print length? values
4


Массивы


Для реализации массивов используются блоки.

arr: [
    [1   2   3  ]
    [10  20  30 ]
    [a   b   c  ]
]

probe arr/1
; --> [1 2 3]
probe arr/2/2
; --> 20

arr/1/2: 5
probe arr/1
; --> [1 5 3]
arr/2/3: arr/2/2 + arr/2/3
probe arr/2/3
; --> 50


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

block: copy []
repeat n 5 [append block n]
probe block
; --> [1 2 3 4 5]


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

Создание блоков


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

probe compose [(1 + 2) 3 4]
; --> [3 3 4]
probe compose ["Текущее время" (now/time)]
; --> ["Текущее время" 13:28:10]


Функции
В Red существует несколько видов функций:

  • Нативные (Native) — функции, вычисляемые непосредственно процессором.
  • Пользовательские — функции, определяемые пользователем.
  • Мезанин-функции (Mezzanine) — функции, являющиеся частью языка, однако не относящиеся к числу нативных.
  • Операторы — функции, используемые как инфиксные операторы (например, +, –, * и /)
  • Рутины (Routine) — функции, используемые для вызова функций внешних библиотек.


Выполнение функций


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

colors: [red green blue]
insert tail insert colors 'yellow 'black
probe colors
; --> [yellow red green blue black]


Уточнения


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

blk: [1 2 3 4 5]
insert/part/dup blk [6 7 8 9] 2 3
probe blk
; --> [6 7 6 7 6 7 1 2 3 4 5]

blk: [1 2 3 4 5]
insert/dup/part blk [6 7 8 9] 2 3
probe blk
; --> [6 7 8 6 7 8 1 2 3 4 5]


Определение функций


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

print-time: does [print now/time]
print-time
; --> 13:18:13


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

func spec body


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

average: func [
    arg1 arg2
][
    sum: arg1 + arg2 
    sum / 2
]


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

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

print average 8 14
; --> 11


Стоит обратить внимание, что в приведенном примере переменная sum, определенная в теле функции, по умолчанию становится глобальной и ее значение доступно за пределами функции. Для того, чтобы переменная оставалась локальной, она должна быть описана в блоке, определяющем аргументы функции, с уточнением /local. После уточнения /local можно указать целый список переменных, объявляемых локальными:

evaluation: func [
    arg1 arg2
    /local sum length depth
][
; ... тело функции
]


Пользовательская функция также может быть определена с помощью функции function, которая идентична функции func с тем отличием, что все переменные, определенные в ее теле, по умолчанию являются локальными. Таким образом, при работе с функцией function не требуется предварительно объявлять локальные переменные с уточнением /local.

Выход из функций и возврат значений


По умолчанию пользовательская функция возвращает последнее вычисленное в ее теле значение. С помощью функции return можно прервать выполнение пользовательской функции в заданной точке и вернуть значение.

iteration: func [i][
    repeat count i [if count = 5 [return count]]
    none
]

print iteration 3
; --> none
print iteration 7
; --> 5


Для прерывания выполнения пользовательской функции в заданной точке без возврата значения используется функция exit.

iteration: func [i][
    repeat count i [if count > 5 [print "Stop!" exit]]
    none
]

print iteration 7
; --> Stop!


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

Создание объектов


Новые объекты создаются с помощью функции make, имеющей следующий формат:

new-object: make parent-object new-values


Здесь new-object — имя создаваемого нового объекта.
Первый аргумент функции, parent-object, является родительским объектом, на основании которого создается новый объект. Когда родительского объекта нет, как в случае создания первого объекта заданного типа, то тогда в его качестве указывается тип данных object!

new-object: make object! new-values


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

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

example: make object! [
    var1: 10
    var2: var1 + 10
    F: func [val][
        var1: val
        var2: val + 20
    ]
]


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

example2: make example []

example3: make example [
    var1: 30
    var2: var1 + 15
]


Объект, создаваемый на основе другого объекта, можно расширить, добавив в него новые переменные:

example4: make example [
    var3: now/date
    var4: "Депозит"
]


Доступ к объектам


Доступ к переменным объекта осуществляется с помощью указания путей к ним. Используя пути, можно изменить переменные объекта или выполнить инкапсулированные в него функции:

example/var1: 37
print example/var1
; --> 37

example/F 100
print example/var2
; --> 120


Доступ к переменным объекта также можно получить с помощью функции in, которая извлекает соответствующее слово из контекста объекта. Далее с помощью функции set можно задать значение выбранной переменной, а с помощью функции get — получить это значение:

print in example 'var1
; --> var1

set in example 'var1 52
print get in example 'var1
; --> 52


Полезные ссылки:


Официальная страница
Полный список функций Red с примерами
Сообщество

© Habrahabr.ru