Краткое пособие по языку Red

в 13:38, , рубрики: dsl, open source, rebol, Red, Программирование, реактивное программирование, функциональное программирование

Публикуемая статья в части, посвященной синтаксису языка, базируется на документации по языку Rebol, оригинал которой доступен по следующему адресу:
www.rebol.com/docs/core23/rebolcore.html


Привет всем!

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

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

Краткое пособие по языку Red - 1

О языке Red

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

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

Краткое пособие по языку Red - 2
Сравнение выразительности языков программирования (источник)

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

Краткое пособие по языку Red - 3

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

Автор: Денис

Источник

Поделиться