REBOL: Последовательности
Рабочие материалы по почти функциональному и почти объектному языку программирования

Последовательности: как нарисовать множество

© Ladislav Mecir — основа статьи
© «Бакава» Анохин — перевод, дополнения и изменения

Содержание: 1. Множества и математика
2. Множества и ребол
3. Трансформатор для последовательностей
4. Разница между методами доступа
5. Зачем реболу столько методов доступа?
6. Длина последовательности
7. С головы до хвоста и обратно

1. Множества и математика

Что такое множество? Кантор ответил на этот вопрос так: «Множество — это объединение в одно целое объектов, хорошо различимых нашей интуицией или нашей мыслью». Таким образом было задано понятие, которое может быть разъяснено, должно быть, любому мыслящему существу.

Понятие множества является изначальным, т.е. не требует определения. Это идея, и, как и многие идеи, некоторые множества довольно сложно описать словами, а иные и представить себе практически невозможно. Широко известен вопрос, вскрывающий некоторые трудности в понимании теории множеств: содержит ли множество, содержащие все множества, само себя? С другой стороны, никого не удивляет, что слово «слова» и определяет всевозможные слова, и само является словом.

2. Множества и ребол

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

[ 1 3 4 "privet" #A {который час}]
== [ 1 3 4 "privet" #A {который час}]

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

a: [ 1 3 4 "privet" #A {который час}]
== [ 1 3 4 "privet" #A {который час}]
a/3 ; берём третий элемент с начала
== 4
a/3 + a/1 ; вычисляем сумму третьего и первого элементов
== 5

Итак, мы умеем задавать последовательности и даже выбирать из них элементы. Теперь давайте подойдём к изучению последовательностей более системно.

Существуют несколько методов доступа к данным внутри последовательности. Во-первых, мы можем использовать числительные:

fifth a ; пятый элемент

Во-вторых, мы можем использовать запись в виде пути (это единообразный метод доступа для различных структур):

a/1 ; == 1

Кроме того, существует функция pick, аргументами которой служат название последовательности и индекс, а результатом является искомое значение:

pick a 5 ;
== #A

Усложним наши простые вычисления: пусть индекс хранится в переменной index. Зададим её:

index: 5
== 5

Как же добраться к пятому значению в последовательности а, не используя явно число пять? Ясно, что числительные нам не особенно помогут (разве что мы напишем парсер, который будет определять нужное нам числительное по значению переменной, но это настолько далеко уведёт нас от первоначальной цели, что мы не будем в это зарываться).

Воспользуемся путевой нотацией:

a/:index ; обратите внимание на двоеточие перед индексом
== #A

Теперь давайте используем функцию pick:

pick a index
== #A

2.1 А почему не срабатывает a/index?

Если вы имеете хоть сколько-нибудь любознательности, вы не могли пройти мимо загадочного двоеточия. На самом деле, а/index тоже работает, но не принимает слово ’i в качестве переменной. Например:

index: 20
b: [1 index 3]
b/index ; == 3

Что произошло? Просто интерпретатор считает, что результатом вычисления выражения b/index является значение, следующее за значением слова index. Например,

charr: #4
q: [#1 #2 #3 charr #5 #6]
q/#4
** Script Error: Invalid path value: 4
** Near: q/#4
q/charr
== #5

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

Но всё-таки такая многозначительность иногда сбивает с толку. Именно поэтому с rebol/core 2.6.0 работает и запись индекса в скобках:

index: 3
w: [1 i 3]
w/(i) ; == 3

Итак, мы окружили индекс скобками, переменная вычислилась и мы получили третий элемент последовательности. Этот странный пример сработает и в предыдущей версии (догадаетесь, почему?), так что поэкспериментируйте сами.

3. Трансформатор для последовательностей

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

a: [1 2 3 4]
change a 3
probe a
== [3 2 3 4]

Путевая запись? Легко!

a: [3 2 3 4]
a/1: 1
== [1 2 3 4]

Уточнить месторасположение изменяемого элемента поможет функция poke:

a: [1 2 3 4]
poke a 1 3
== [3 2 3 4]

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

Сперва используем путь:

a: [1 2 3]
i: 2
a/:i: 4
a
== [1 4 3]

Теперь функцию poke:

a: [3 2]
i: 2
poke a i 4 ; == [3 4]

4. Разница между методами доступа

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

Элементом последовательности может быть функция:

series2: [1 2]
f: does [""]
poke series2 1 :f

4.1 Доступ с помощью числительных

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

third series2
** Script Error: Out of range or past end
** Near: third series2
? series2
== series2 is a block of value: [func [][""] 2]

А теперь давайте посмотрим на тип первого элемента из series2:

type? first series2 ; == function!

4.2 Доступ с помощью путевой нотации

Если мы пытаемся получить значение, которого не существует, мы получим вместо него значение none:

series2/3
== none

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

type? series2/1
== string!

4.3 Доступ с помощью PICK

Если мы пробуем получить значение, которого не существует, мы получаем значение none:

pick series2 3
== none

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

type? pick series2 1
== function!

5. Зачем реболу столько методов доступа?

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

Если нам нужно проверять код на правильность, лучше использовать числительные.

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

Если нам нужно просто вынуть значение без вычислений, то выбор однозначен: pick.

6. Длина последовательности

Если нужно узнать число значений в последовательности, используйте функцию length?:

length? [1 2 3] ; == 3

С другой стороны, бывают и пустые последовательности. Для отделения их от непустых служит слово empty?:

series3: [] ; == []
empty? series3 ; == true
length? series3 ; == 0

7. С головы до хвоста и обратно

Для последовательного доступа к значениям внутри упорядоченной последовательности используются встроенные методы. Так, для того, чтобы получить следующее значение в последовательности, используется слово next. next «пропускает» первый элемент и возвращает оставшуюся последовательность. Второй элемент первоначальной последовательности становится первым элементом в новой.

series8: [1 2 3 4]
== [1 2 3 4]
series9: next series8
== [2 3 4]

series9/1
== 2
series8/2
== 2
series9/2: 5
== [2 5 4]
series8/3
== 5

Мы можем пропустить более чем один элемент за один раз. Для неотрицательных смещений нужно вызвать слово skip. Если мы пропустим ноль элементов, то получим первоначальную последовательность:

series8: [1 2 3 4]
== [1 2 3 4]
same? series8 skip series8 0
== true

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

tail-def: func [
{Returns the tail of a series.}
series [series!]
][
skip :series length? :series
]
tail-def [1 2 3] ; == []

Мы не можем пропустить хвост, будто элементы перед ним, это просто не получится:

same? next tail [1 2 3] tail [1 2 3] ; == true

Зато мы можем повернуться на 180 градусов и двинуться в обратную сторону, к голове последовательности:

series11: tail [1 2 3 4]
== []
series12: back series11
== [4]

Я уже упомянул голову последовательности — так вот, это просто самая длинная последовательность, полученная продвижением от хвоста. Дальше пятиться некуда!

series14: next [1 2 3]
== [2 3]
head series14
== [1 2 3]
same? back head series14 head series14
== true
Formatted with MakeDoc2 by REBOL - 21-Apr-2007