Хабрахабр

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

Привет всем!

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

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

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

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


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

Из графика видно, что 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/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, таких как блоки, строки, пути и т.д., представляют собой серии.
В общем виде серия выглядит следующим образом:

  • голова (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 с примерами
Сообщество

Теги
Показать больше

Похожие статьи

Кнопка «Наверх»
Закрыть