2. Определение языка


2.1 Объекты


2.1.1 Атомы и ряды

Все данные, обрабатываемые в Euphoria, называются объектами, а объекты могут быть атомами или рядами. Каждый атом -- это единственное число. Каждый ряд -- это набор объектов.

Те объекты, которые содержатся в ряде, могут быть произвольной смесью атомов и рядов. Ряд представляется списком объектов, заключённым в фигурные скобки. Объекты в этом списке разделены запятыми. Атомы могут иметь любую величину в пределах от приблизительно -1e300 (минус 10 в степени 300) до +1e300 (10 в степени 300) с 15-ю значащими десятичными цифрами -- целую или двойной точности с плавающей точкой. Вот некоторые примеры объектов Euphoria:

        -- примеры атомов:
        0
        1000
        98.6
        -1e6

        -- примеры рядов:
        {2, 3, 5, 7, 11, 13, 17, 19}
        {1, 2, {3, 3, 3}, 4, {5, {6}}}
        {{"jon", "smith"}, 52389, 97.25}
        {}                        -- пустой ряд

Числа могут быть шестнадцатеричными. Например:

        #FE             -- 254
        #A000           -- 40960
        #FFFF00008      -- 68718428168
       -#10             -- -16

В шестнадцатеричных числах разрешены только заглавные буквы A, B, C, D, E, F. Шестнадцатеричные числа всегда положительные, если только перед символом # не стоит знак минус. То есть, например, #FFFFFFFF - это громадное положительное число (4294967295), а *не* -1, как могут ожидать некоторые программисты, практикующие программирование на машинном языке.

Ряды могут быть вложенными на любую глубину, т.е. вы можете иметь ряды внутри рядов внутри рядов и так далее до любого уровня (пока будет хватать памяти на вашей машине). Фигурные скобки можно использовать, чтобы конструировать ряды из списка выражений. Эти выражения могут быть постоянными или вычисляться во время исполнения программы, например:

        {x+6, 9, y*w+2, sin(0.5)}

Слова "Hierarchical Objects" - "Иерархические Объекты" в акрониме Euphoria отражают именно иерархическую многоуровневую структуру вложенных рядов. Не следует путать это с иерархией классов в некоторых объектно-ориентированных языках.

Почему же мы назвали их атомами? Почему не просто "числами"? Хорошо, нет проблемы, атом является просто числом, но мы хотели иметь отдельный термин, подчёркивающий тот факт, что атом принципиально отличается от ряда и не имеет неких более элементарных составляющих частей среди объектов Euphoria. Конечно, в мире физики "неделимые" атомы были расщеплены на элементарные частицы много лет назад, но в Euphoria они остаются основными строительными блоками для всех данных, которыми Euphoria манипулирует. Согласно же нашей "химической" аналогии, ряды можно представить себе как некие "молекулы", собранные из атомов и других молекул. Но лучшей аналогией являются каталоги и файлы на диске компьютера. Точно так же как каталог на диске может содержать и файлы, и другие каталоги, ряд может содержать и атомы, и другие ряды (а те ряды могут содержать атомы и другие ряды, и так далее).

Как вы скоро сами убедитесь, ряды делают Euphoria очень простой и очень мощной. Понимание атомов и рядов является ключом к пониманию Euphoria.

Примечание о производительности:
Не означает ли это, что все атомы размещены в памяти как 8-байтовые числа с плавающей точкой? Нет. Интерпретатор Euphoria обычно размещает целочисленные атомы как машинные целые (4 байта), чтобы сэкономить память и увеличить скорость вычислений. Но когда результат становится дробным или число слишком увеличивается, автоматически производится преобразование к формату с плавающей точкой.

2.1.2 Строки символов и отдельные символы

Каждая строка символов -- это просто ряд символов. Строка может быть введена с использованием кавычек, например:

        "ABCDEFG"

Со строками символов можно делать всё то же самое, что и с любыми другими рядами. Например, та строка, что вы видите выше, полностью эквивалентна ряду:

        {65, 66, 67, 68, 69, 70, 71}
который содержит численные коды ASCII, соответствующие символам. Компилятор Euphoria немедленно преобразует "ABCDEFG" в показанный выше ряд чисел. В этом смысле в Euphoria нет "строк", а есть только ряды чисел. Строка в кавычках на самом деле является просто удобной формой записи, чтобы избавить вас от необходимости иметь под рукой таблицу со всеми кодами ASCII и от ввода текста как ряда чисел, разделённых запятыми.

Это означает, что "" эквивалентно {}. Обе записи представляют ряд длины 0, также известный как пустой ряд. Что касается стиля программирования, то естественно писать "", чтобы показать символьный ряд длины 0, и {}, чтобы показать некоторый другой сорт пустого ряда.

Каждый отдельный символ является атомом. Его следует вводить, используя одинарные кавычки. Имеется отличие отдельного символа (который является атомом) от строки символов, содержащей единственный символ, которая представляет собой ряд с длиной 1. То есть,

        'B'   -- эквивалентно атому 66 - код ASCII для B
        "B"   -- эквивалентно ряду {66}

Повторим, 'B' - это просто вариант записи, эквивалентный числу 66. В Euphoria, по сути, нет никаких символов, а есть только числа (атомы).

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

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

        \n        новая строка - перевод строки, LF
        \r        возврат каретки, CR
        \t        табуляция
        \\        собственно косая черта (солидус)
        \"        двойная кавычка
        \'        одинарная кавычка

Например, "Hello, World!\n", или '\\'. Редактор Euphoria отображает строки символов и отдельные символы в зелёном цвете.


2.1.3 Комментарии

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

        -- это комментарий

Комментарии игнорируются компилятором и никак не влияют на скорость исполнения программы. Редактор отображает комментарии в красном цвете.

В самой первой строке вашей программы (и только в ней) вы можете использовать специальный комментарий, начинающийся с #!, то есть,

        #!/home/rob/euphoria/bin/exu  
Подобный комментарий информирует командный интерпретатор Linux, что ваш файл следует исполнять под управлением интерпретатора Euphoria, и дает полный путь к интерпретатору Euphoria exu. Если вы измените атрибуты этого вашего файла, обозначив его как исполняемый, вы сможете запускать его, просто введя его имя в командной строке как это делается со стандартными командами Linux, то есть вам уже не нужно будет предварительно вводить "exu". Под DOS и Windows эта первая строка с #! трактуется просто как комментарий (хотя сервер Web Apache под Windows и распознает её). Если вы окутываете свой файл (.il), вместо exu следует указывать backendu.



2.2 Выражения

Подобно другим языкам программирования, Euphoria позволяет вам выполнять вычисления, просто записывая выражения для результатов. Однако в Euphoria вы можете выполнить вычисления на целом ряде данных в одном-единственном выражении, тогда как в большинстве других языков вам пришлось бы конструировать цикл. В Euphoria вы можете обрабатывать сразу весь ряд на манер отдельного числа. Ряд может быть скопирован, подан в подпрограмму и обсчитан как единое целое. Например,

        {1,2,3} + 5
является выражением, в котором ряд {1,2,3} и атом 5 складываются и дают результирующий ряд {6,7,8}.

Чуть позже мы приведем множество других примеров.


2.2.1 Операции сравнения

Операции сравнения выполняются в выражениях с помощью операторов <   >   <=   >=   =   !=  , результатом работы каждого из которых является 1 (истина, да) или 0 (ложь, нет).

        8.8 < 8.7   -- 8.8 меньше чем 8.7 (0,нет)
        -4.4 > -4.3 -- -4.4 больше чем -4.3 (0,нет)
        8 <= 7      -- 8 меньше чем или равно 7 (0,нет)
        4 >= 4      -- 4 больше чем или равно 4 (1, да)
        1 = 10      -- 1 равно 10 (0, нет)
        8.7 != 8.8  -- 8.7 не равно 8.8 (1, да)

Как мы скоро увидим, эти операции могут выполняться и с рядами.


2.2.2 Логические операции

Логические операции выполняются в выражениях с помощью операторов and (И), or (ИЛИ), xor (НО), и not (НЕ), результатом работы каждого из которых является "истинность" или "ложность" выражения, 1 или 0, то есть:

        1 and 1     -- 1 (истина, да)
        1 and 0     -- 0 (ложь, нет)
        0 and 1     -- 0 (ложь, нет)
        0 and 0     -- 0 (ложь, нет)

        1 or  1     -- 1 (истина, да)
        1 or  0     -- 1 (истина, да)
        0 or  1     -- 1 (истина, да)
        0 or  0     -- 0 (ложь, нет)

        1 xor 1     -- 0 (ложь, нет)
        1 xor 0     -- 1 (истина, да)
        0 xor 1     -- 1 (истина, да)
        0 xor 0     -- 0 (истина, да)

        not 1       -- 0 (ложь, нет)
        not 0       -- 1 (истина, да)

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

        5 and -4    -- 1 (истина, да)
        not 6       -- 0 (ложь, нет)

Эти операции также могут применяться и к рядам. См. ниже.

В некоторых случаях выполняется так называемая укороченная проверка истинности. Укороченная проверка производится в выражениях, содержащих операторы and или or.


2.2.3 Арифметические операции

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

        3.5 + 3  -- 6.5
        3 - 5    -- -2
        6 * 2    -- 12
        7 / 2    -- 3.5
        -8.1     -- -8.1
        +8       -- +8

Вычисление результата, который оказывается слишком большим (т.е. вне диапазона от -1e300 до +1e300), порождает один из специальных атомов +infinity (+бесконечность) или -infinity (-бесконечность). На экране эти атомы отображаются как inf или -inf, если результат выводится на печать. Возможно также получение nan или -nan. "nan" означает "not a number" - "не число", т.е. некоторая неопределенная величина (такая как inf деленная на inf). Эти специальные значения введены в Euphoria на основании стандарта IEEE по вычислениям с плавающей точкой. Если вы находите одну из этих специальных величин среди результатов счёта по вашей программе, то обычно это является признаком ошибки в логике вашей программы, хотя inf как промежуточный результат иногда и допустим. Например, 1/inf равно 0, и это может быть "правильным" ответом в вашем алгоритме.

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

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


2.2.4 Операции на рядах

Все операции (сравнения, логические и арифметические), описанные выше, равно как и математические подпрограммы, описанные в документе Часть II - Библиотека подпрограмм, могут быть применены к рядам и к отдельным числам (атомам).

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

        x = -{1, 2, 3, {4, 5}}   -- x равно {-1, -2, -3, {-4, -5}}

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

        x = {5, 6, 7, 8} + {10, 10, 20, 100}
        -- x равно {15, 16, 27, 108}

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

        y = {4, 5, 6}

        w = 5 * y              -- w равно {20, 25, 30}

        x = {1, 2, 3}

        z = x + y              -- z равно {5, 7, 9}

        z = x < y              -- z равно {1, 1, 1}

        w = {{1, 2}, {3, 4}, {5}}

        w = w * y              -- w равно {{4, 8}, {15, 20}, {30}}

        w = {1, 0, 0, 1} and {1, 1, 1, 0}    --  w равно {1, 0, 0, 0}

        w = not {1, 5, -2, 0, 0}     -- w равно {0, 0, 0, 1, 1}

        w = {1, 2, 3} = {1, 2, 4}    -- w равно {1, 1, 0}
        -- обратите внимание, что первый символ '=' - это команда присваивания,
        -- а второй символ '=' - это оператор сравнения, проверяющий равенство
        -- соответствующих элементов рядов попарно с формированием ряда результатов
        -- сравнения, полученных для каждой из пар элементов
Примечание: В вопросе сравнения рядов Euphoria имеется одна не вполне и не сразу заметная тонкость. Когда вы хотите сравнить две строки (или два других ряда), здесь, в отличие от некоторых других языков, не следует использовать оператор '=':
       if "APPLE" = "ORANGE" then  -- ОШИБКА! 
В выражении, стоящем после команды "if" (если), символ '=' трактуется как бинарный оператор (сравнения), подобно другим бинарным операторам вроде '+', '*' и т.д., следовательно, он применяется к соответствующим парам элементов рядов, и эти ряды должны иметь одинаковую длину. Когда условие равенства длин операндов соблюдено, формируется ряд результатов сравнения, состоящий из нулей и единиц, но когда длины не равны, вы получите сообщение об ошибке в вашей программе. С другой стороны, команда "if" ожидает результат вычисления выражения, подаваемого в эту команду, в виде атома, а не ряда, то есть, и при равенстве длин рядов вы получите сообщение об ошибке в вашей программе. Для получения результата сравнения рядов в виде интегральной (число), а не поэлементной (ряд), характеристики, необходимо использовать встроенную функцию equal() (равно):
       if equal("APPLE", "ORANGE") then  -- ПРАВИЛЬНО
В общем-то, для несколько более полного сравнения рядов имеется ещё и встроенная функция compare() (сравнить):
       if compare("APPLE", "ORANGE") = 0 then  -- ПРАВИЛЬНО
Вы можете использовать compare() также и для других сравнений:
       if compare("APPLE", "ORANGE") < 0 then  -- ПРАВИЛЬНО
           -- здесь "APPLE" меньше чем "ORANGE",
           -- так как длина второго ряда больше (истина, 1)


2.2.5 Индексирование рядов

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

Например, если x содержит {5, 7.2, 9, 0.5, 13}, то x[2] равно 7.2. Предположим, что мы присвоили какое-то другое значение x[2]:

        x[2] = {11,22,33}

Тогда x становится равным: {5, {11,22,33}, 9, 0.5, 13}. Теперь, если мы повторим индексирование x[2], то получим {11,22,33}, а если сделаем запрос x[2][3], то результатом будет атом 33. Если вы попытаетесь индексировать ряд числом, которое выходит за пределы диапазона от 1 до числа элементов ряда, вы получите сообщение об ошибке индексирования. Например, x[0], x[-99] или x[6] все вызовут ошибку. То же самое произойдет и для x[1][3], так как x[1] не является рядом в нашем примере. Как такового, предела на число индексов (размерность ряда) не существует, но тогда ряд должен быть достаточно глубоко вложенным. Двумерный массив, так широко распространённый в других языках, может быть легко представлен рядом рядов:

        x = {
             {5, 6, 7, 8, 9},      -- x[1]
             {1, 2, 3, 4, 5},      -- x[2]
             {0, 1, 0, 1, 0}       -- x[3]
            }
где мы записали числа таким образом, чтобы структура ряда проявилась более отчётливо. Выражение в форме x[i][j] даёт доступ к любому элементу.

Но измерения в этом ряде рядов, тем не менее, не вполне симметричны, так как любая "строка" целиком может быть отобрана как x[i], а столь же простого выражения для отбора целой "колонки" не существует. Другие логические структуры, такие как n-мерные массивы, массивы строк, структуры, массивы структур и т.д., могут быть обработаны легко и гибко:

 3-D массив:
        y = {
             {{1,1}, {3,3}, {5,5}},
             {{0,0}, {0,1}, {9,1}},
             {{-1,9},{1,1}, {2,2}}
            }
y[2][3][1] равно 9

Массив строк:

        s = {"Hello", "World", "Euphoria", "", "Last One"}
s[3] равно "Euphoria"
s[3][1] равно 'E'

Структура:

        employee = {
                    {"Джон","Смит"},
                    45000,
                    27,
                    185.5
                   }
Чтобы обращаться к "полям" или элементам внутри структуры в хорошем стиле программирования, целесообразно создать набор констант для имён различных полей структуры. Тогда вашу программу будет легко читать. Для примера выше можно было бы иметь:
        constant NAME = 1
        constant FIRST_NAME = 1, LAST_NAME = 2

        constant SALARY = 2
        constant AGE = 3
        constant WEIGHT = 4
Тогда имя служащего можно получить как employee[NAME], а фамилию - как employee[NAME][LAST_NAME].

Массив структур:

        employees = {
                     {{"John","Smith"}, 45000, 27, 185.5},   -- a[1]
                     {{"Bill","Jones"}, 57000, 48, 177.2},   -- a[2]

                     -- .... и так далее.
                    }
employees[2][SALARY] будет 57000.
Встроенная функция length() выдаёт длину поданного в неё ряда. Следовательно, последний элемент ряда s равен:
        s[length(s)]
Имеется короткая форма записи, сокращение, для подобных операций:
        s[$]
Аналогично,
        s[length(s)-1]
может быть упрощено до:
        s[$-1]
Символ $ равен длине ряда. Сокращение $ можно использовать только внутри квадратных скобок. Если ряды вложенные, то есть:
        s[$ - t[$-1] + 1]
первый $ относится к длине s, а второй - к длине t (как вы, возможно, и ожидали). Ниже приведён пример, в котором $ может избавить вас от лишней работы, делая ваш код более ясным и, вероятно, даже более быстрым:
        longname[$][$] -- последний элемент последнего элемента
Сравните с обычной формой записи той же самой операции:
        longname[length(longname)][length(longname[length(longname)])]
Индексирование и функциональные побочные эффекты. Рассмотрим команду присваивания, когда в её левой части выполняется индексирование:
    lhs_var[lhs_expr1][lhs_expr2]... = rhs_expr
Здесь все вычисления выражений и любое индексирование производится слева направо. В выражении из правой части команды присваивания или в любом их левых выражений возможно использование вызова функции. Если при вызове функции возникает побочный эффект, приводящий к модификации lhs_var, то теряется определённость, появятся ли эти модификации в lhs_var, когда выполнение команды присваивания будет завершено. Чтобы быть вполне уверенным в том, что ваши ожидания исполнятся именно так, как вы этого хотите, следует выполнять вызов функции в отдельной команде, а не пытаться модифицировать lhs_var двумя различными путями в одной и той же команде. В тех случаях, когда в левой части команды присваивания индексирование отсутствует, вы можете всегда считать, что окончательное значение lhs_var будет равно значению rhs_expr, вне зависимости от любых побочных эффектов, которые могли бы изменить lhs_var.

Структуры данных Euphoria имеют почти бесконечную гибкость. Массивы в других языках вынуждены иметь фиксированное число элементов, и эти элементы должны все быть одного и того же типа. Euphoria снимает оба эти ограничения. Вы можете легко добавить новую структуру в ряд employee (см.выше), или разместить необычайно длинное имя в поле NAME, и Euphoria позаботится обо всех этих изменениях автоматически. Если вы хотите, вы можете поместить множество различных "структур" employee, с различными размерами, все в один ряд.

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

Имейте в виду, что выражения в общем не могут быть индексированы, индексировать можно только переменные и константы. Например: {5+2,6-1,7*8,8+1}[3] не поддерживается. Нельзя делать и что-либо вроде date()[MONTH]. Вы должны присвоить значение ряда, выдаваемого date(), переменной или константе, а затем индексировать их обычным путём, чтобы получить желаемый результат.


2.2.6 Сечение рядов

Из ряда могут быть выделены несколько последовательных элементов путём задания в квадратных скобках номеров первого и последнего элементов выделяемого участка (отрезка). Например, если x равно {1, 1, 2, 2, 2, 1, 1, 1}, то отрезок x[3..5] будет представлять собой ряд {2, 2, 2}. x[3..3] будет равно ряду {2}. x[3..2] также разрешено, и выдает пустой ряд длины ноль {}. Если y имеет значение: {"fred", "george", "mary"}, то y[1..2] равно {"fred", "george"}.

Мы можем также использовать отрезки для переписывания порций ряда. После команды x[3..5] = {9, 9, 9} x будет равно {1, 1, 9, 9, 9, 1, 1, 1}. С тем же эффектом мы могли бы записать x[3..5] = 9. Предположим y равно {0, "Euphoria", 1, 1}. Тогда y[2][1..4] будет "Euph". Если мы напишем y[2][1..4]="ABCD", то y станет равным {0, "ABCDoria", 1, 1}.

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

В отношении пустых отрезков следует иметь в виду следующие детали. Рассмотрим отрезок s[i..j], где s имеет длину n. Отрезок от i до j, где j = i-1 и i >= 1 создает пустой ряд, даже если i = n+1. Таким образом, 1..0 и n+1..n, и все другие варианты внутри являются законными (пустыми) отрезками. Пустые отрезки весьма полезны во многих алгоритмах. Но отрезок от i до j, где j < i - 1 незаконен, т.е. "вывернутые" отрезки, такие как s[5..3], не разрешены.

С отрезками можно использовать сокращение $, то есть:

        s[2..$]
        s[5..$-2]
        s[$-5..$]
        s[$][1..floor($/2)] -- первая половина последнего элемента s

2.2.7 Сцепление рядов и атомов - операция &

Над любыми двумя объектами может быть произведена операция сцепления (конкатенации). Для этого служит оператор & - просто "и" в переводе на русский язык. Результатом операции является ряд, имеющий длину, равную сумме длин сцепленных объектов (здесь атомам как бы приписывается длина 1). То есть:

        {1, 2, 3} & 4              -- {1, 2, 3, 4}

        4 & 5                      -- {4, 5}

        {{1, 1}, 2, 3} & {4, 5}    -- {{1, 1}, 2, 3, 4, 5}

        x = {}
        y = {1, 2}
        y = y & x                  -- y всё ещё остается равным {1, 2}

Вы можете удалить элемент i любого ряда s, сцепив отрезки ряда до и после ненужного элемента i:

	s = s[1..i-1] & s[i+1..length(s)]
Этот приём работает, даже когда i равно 1 или length(s), так как s[1..0] является законным пустым отрезком, так же как и s[length(s)+1..length(s)].


2.2.8 Формирование ряда

Операция формирования ряда записывается с помощью фигурных скобок и запятых:

        {a, b, c, ... }
В ней участвует n операндов, где n равно 0 или более. В результате операции из величин элементов создается n-элементный ряд. То есть,
        x = {apple, orange*2, {1,2,3}, 99/4+foobar}

Операция формирования ряда выполняется в последнюю очередь, см. Порядок операций.


2.2.9 Другие операции на рядах

Для обозначения некоторых других важных операций, которые вы можете выполнять, используются английские слова, а не специальные символы, о которых шла речь ранее. Эти операции выполняются с помощью функций, встроенных в интерпретаторы ex.exe/exw.exe/exu, следовательно, они всегда под рукой, следовательно, они очень быстрые. Они детально описаны в документе Часть II - Библиотека подпрограмм, но очень важны для программирования на Euphoria, поэтому мы должны упомянуть о них здесь, прежде чем продолжить разговор о других вещах. Эти операции запускаются на исполнение так, как если бы они были подпрограммами, хотя на самом деле их воплощение значительно более эффективно, чем обычных подпрограмм.


length (длина)

Функция length(s) выдаёт вам значение текущей длины ряда s. Это число элементов в ряде s. Некоторые из этих элементов сами могут быть рядами, содержащими другие элементы, но наша функция даёт число элементов самого "верхнего" уровня, считая каждый ряд на этом уровне за единственный элемент. При попытке определения длины атома выдаётся сообщение об ошибке, то есть,

        length({5,6,7})             -- 3
        length({1, {5,5,5}, 2, 3})  -- 4 (не 6!)
        length({})                  -- 0
        length(5)                   -- ОШИБКА!

repeat (повторить)

Функция repeat(item, count) выдает ряд, который состоит из элементов 'item', повторенных 'count' раз, то есть,

        repeat(0, 100)         -- {0,0,0,...,0}  т.е. 100 нулей
        repeat("Hello", 3)     -- {"Hello", "Hello", "Hello"}
        repeat(99,0)           -- {}

Элемент, подлежащий повторению, может быть любым атомом или рядом.


append / prepend (добавить / прибавить)

Функция append(s, item) выдаёт новый ряд, добавляя элемент 'item' после конца ряда 's'. Функция prepend(s, item) выдаёт новый ряд, прибавляя элемент 'item' перед началом ряда 's', то есть,

        append({1,2,3}, 4)         -- {1,2,3,4}
        prepend({1,2,3}, 4)        -- {4,1,2,3}

        append({1,2,3}, {5,5,5})   -- {1,2,3,{5,5,5}}
        prepend({}, 9)             -- {9}
        append({}, 9)              -- {9}

Длина нового ряда всегда на 1 больше, чем длина исходного ряда. Присоединяемый элемент может быть любым атомом или рядом.

Эти две встроенные функции, append() и prepend(), в чём-то похожи на оператор сцепления, &, но имеется и вполне ясное отличие, то есть,

        -- присоединение ряда отличается
        append({1,2,3}, {5,5,5})   -- {1,2,3,{5,5,5}}
        {1,2,3} & {5,5,5}          -- {1,2,3,5,5,5}

        -- присоединение атома идентично
        append({1,2,3}, 5)         -- {1,2,3,5}
        {1,2,3} & 5                -- {1,2,3,5}

2.2.10 Порядок операций

Порядок выполнения операций интерпретатором показан в таблице:

        первая очередь:     вызовы функций/типов

                                unary-  unary+  not

                                *  /

                                +  -

                                &

                                <  >  <=  >=  =  !=

                                and  or  xor

        последняя очередь:      { , , , }

Следовательно, 2+6*3 означает 2+(6*3), а не (2+6)*3. Операторы, расположенные в таблице в одной строке, имеют одинаковую очерёдность и поэтому выполняются в том порядке, как записаны в выражении слева направо. Вы можете задать любой другой необходимый вам порядок операций, воспользовавшись в вашем выражении круглыми скобками ( ).

Символ равенства '=', используемый для записи не только операции сравнения, но и команды присваивания, не создаёт двусмысленностей в программе, так как его конкретное назначение вполне ясно из контекста.



2.3 Euphoria против других языков

Положив в основу Euphoria ряды как единственную, простую, общую, рекурсивную структуру данных, удалось избежать громадного объёма сложностей, обычно присутствущих в языках программирования. Массивы, структуры, союзы, массивы записей, многомерные массивы и другие тому подобные объекты других языков все могут быть легко представлены рядами. И структуры высшего уровня, такие как списки, стеки, архивы, очереди, деревья и т.д., тоже не ушли от этой участи.

Более того, в Euphoria вы можете иметь ряды смешанного типа; вы можете присвоить значение любого объекта элементу ряда; и ряды легко растут и сокращаются без всяких дополнительных усилий со стороны программиста по поводу вопросов выделения и освобождения памяти. Точная заблаговременная раскладка данных в структуры не требуется и она может быть создана и изменена динамически по мере необходимости. Легко пишется код общего характера, где, например, вы подаёте и извлекаете из единственного стека объекты разнообразного рода данных. Создание гибкого списка, который содержит разнообразные объекты данных, тривиально в Euphoria, но требует десятков строк уродливого кода в других языках.

Манипуляции с данными весьма эффективны, так как интерпретатор Euphoria не копирует большие объекты, а указывает на уже существующий экземпляр.

Программирование на Euphoria полностью основано на создании и преобразовании гибких динамических рядов данных. Всё это ряды - здесь нет других структур данных для изучения. Вы оперируете в простом, безопасном, податливом мире величин, который находится далеко от жёсткого, утомительного, опасного мира битов, байтов, пойнтеров и машинных крахов.

В отличие от других языков, таких как LISP и Smalltalk, Euphoria выполняет "приборку" неиспользуемой памяти в непрерывном процессе, который никогда не вызывает случайных задержек в исполнении программы и не требует предварительного резервирования огромных участков памяти.

Определения обычных языков, таких как C, C++, Ada и т.д., очень сложны. Большинство программистов осваивает только некоторое подмножество возможностей языка. Стандарты ANSI на эти языки читаются как сложные своды законов.

Вас вынуждают писать разный код для разных типов данных, даже чтобы просто скопировать данные, получить текущую длину, сцепить их, сравнить их и т.д. Руководства по этим языкам переполнены функциями, такими как "strcpy", "strncpy", "memcpy", "strcat", "strlen", "strcmp", "memcmp", и т.п., каждая из которых работает только с одним из многих типов данных.

Слишком много сложностей вокруг вопроса о типах данных. Как определить новые типы? Какие типы данных могут быть смешанными? Как преобразовать один тип в другой, да ещё чтобы компилятор остался счастливым? Когда вам нужно что-то, требующее гибкости во время исполнения программы, вы часто находите себя за занятием, которое очень смахивает на попытки надувательства компилятора.

В этих языках численная величина 4 (к примеру) может иметь различный смысл в зависимости от того, что это за тип: int, char, short, double, int* и т.д. В Euphoria 4 - это атом 4, точка! В Euphoria есть кое-что, также названное типом, но это намного более простая концепция, как мы увидим чуть позже.

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

В других языках широко используются переменные-указатели (пойнтеры). Пойнтеры следовало бы назвать "goto" для структур данных. Они заставляют программиста думать о данных как о закреплённых в памяти огороженных участках, на которых производятся разного рода низкоуровневые, непереносимые на другие платформы трюки. Картина медных кишочек машины, на которой будет исполняться программа, то и дело всплывает в воображении программиста. В Euphoria нет пойнтеров и она не нуждается в них.



2.4 Объявления


2.4.1 Имена (идентификаторы)

Идентификаторы, к которым относятся имена переменных, подпрограмм и других структурных единиц программы, могут иметь любую длину. Строчные и заглавные буквы различаются. Имена должны начинаться с буквы, а затем могут содержать цифры и знаки подчерка - '_'. В системе существует группа занятых слов (их иногда ещё называют зарезервированными, резервными или ключевыми словами), имеющих особое значение в Euphoria, и которые не могут быть использованы в качестве имен:

    and            end             include          to
    by             exit            not              type
    constant       for             or               while
    do             function        procedure        with
    else           global          return           without
    elsif          if              then             xor

Редактор Euphoria отображает эти занятые слова в синем цвете.

Идентификаторы служат для объявления и последующего использования следующих структурных единиц (элементов) языка:

  • процедур
  • функций
  • типов
  • переменных
  • констант (постоянных)


процедуры

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

        procedure empty()
        end procedure


        procedure plot(integer x, integer y)
            position(x, y)
            puts(1, '*')
        end procedure

В круглых скобках, следующих за именем объявляемой процедуры, вы через запятые пишете список аргументов (если они вам необходимы). В первом примере пустая процедура не имеет аргументов. Во втором примере хотя и показан список, состоящий всего из двух аргументов, это не означает, что допустимая длина списка как-то ограничена. Кроме того, каждый из аргументов может быть рядом произвольной структуры и длины. Во многих языках списки агрументов с переменной длиной вообще невозможны. А в языке C вы должны для этого установить странные механизмы, которые настолько сложны, что среднему программисту приходится каждый раз перечитывать руководство или консультироваться с местными гениями.

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

Примечание о производительности:
Интерпретатор на самом деле копирует ряды или числа с плавающей точкой только в тех случаях, когда это становится действительно необходимо, например:
            y = {1,2,3,4,5,6,7,8.5,"ABC"}
            x = y
Команда присваивания x = y фактически не создает новую копию y, содержащуюся в x. После выполнения этой команды и x, и y будут просто "указывать" на один и тот же ряд, заданный выражением для y ранее. Если мы позже выполним x[3] = 9, то для x в памяти будет создана новая копия (хотя всё ещё будет только одна общая (для x и y) копия числа с плавающей точкой 8.5 и для внутреннего ряда "ABC"). Такой же приём работает по отношению к "копиям" аргументов, подаваемых в подпрограммы (подпрограмма - это общий термин для процедур, функций и типов).

функции

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

        function max(atom a, atom b)
            if a >= b then
                return a
            else
                return b
            end if
        end function

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

        return {x_pos, y_pos}

Мы будем использовать общий термин "подпрограмма", когда наше замечание относится одновременно и к процедурам, и к функциям.


типы

В Euphoria имеются специальные функции, которые используются для объявления допустимых величин переменных. Тип должен иметь строго один аргумент и выдавать атом, который может иметь два значения - истина (не-нуль) или ложь (нуль). Типы также могут быть вызваны подобно другим функциям. См. 2.4.3 Задание типа переменной.


переменные

Переменным могут быть присвоены значения во время исполнения программы, например,

        -- x можно присвоить только целочисленную величину
        integer x
        x = 25

        -- a, b и c можно присвоить *любую* величину
        object a, b, c
        a = {}
        b = a
        c = 3.14

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


константы (постоянные)

Константы - это такие переменные, которым однажды присваивается некоторая начальная величина и изменить эту величину в дальнейшем уже невозможно, например,

        constant MAX = 100
        constant Upper = MAX - 10, Lower = 5
        constant name_list = {"Fred", "George", "Larry"}

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

Константы не могут быть объявлены внутри объявления подпрограмм.


2.4.2 Сцена (область видимости)

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

В Euphoria имеется множество предопределённых (так сказать, заранее объявленных, а точнее, встроенных) процедур, функций и типов. Все они объявляются автоматически перед стартом любой программы. Редактор Euphoria отображает их имена в малиновом цвете. Эти предопределённые имена не являются занятыми словами, и вы можете переопределить их своими собственными подпрограммами.

Каждое новое имя в вашей программе перед его дальнейшим использованием должно быть объявлено, а структурная единица программы (переменная, подпрограмма, константа) фигурирующая под этим именем, должна быть вами определена. Такая дисциплина, действующая в Euphoria, позволяет вам читать программу последовательно от начала и до конца без необходимости забегания вперёд, чтобы раскрыть значение заинтриговавшего вас слова, которое уже вовсю используется автором программы, но действительный смысл которого ещё окутан тайной, которая скрывается где-то в дебрях кода, спутанного как итальянские макароны в кастрюле. Тем не менее, если необходимость в опережающей ссылке на подпрограмму существует и оправдана упрощением кода, вы можете предусмотреть вызов подпрограммы по особому её номеру, значение которого будет вычислено в вашей программе позже, но не по имени, которое ещё не объявлено. Для этого в Euphoria имеются специальная функция, routine_id(), и, соответственно, подпрограммы call_func() или call_proc(), одна для функций, а другая для процедур. См. Часть II - Библиотека подпрограмм - Динамические вызовы.

В процедурах, функциях и типах разрешены рекурсивные вызовы, когда подпрограмма вызывает саму себя. Взаимная рекурсия, где подпрограмма A вызывает подпрограмму B, которая прямо или косвенно вызывает подпрограмму A, также возможна, но требует использования механизма routine_id().

Имя считается известным интерпретатору от точки, в которой оно было объявлено, до границы его сцены. Сцена переменной, объявленной внутри подпрограммы (частной переменной) ограничена концом подпрограммы. Сцена всех других переменных, констант, процедур, функций и типов ограничена концом файла исходного кода, в котором они объявлены, и все они называются местными (локальными). Если объявлению на уровне файла предшествует ключевое слово global (глобальный), то сцена в этом случае может простираться за пределы файла, в котором записано объявление, но правила, по которым сцена ограничивается, становятся не слишком простыми.

Когда вы включаете библиотечный файл Euphoria в свой главный файл (см. 2.6 Специальные команды высшего уровня), то только переменные и подпрограммы, объявленные в библиотечном файле с использованием ключевого слова global, оказываются доступными или даже видимыми в вашем главном файле. Другие, не-глобальные, объявления в библиотечном файле видны интерпретатору только до конца библиотечного файла, и вы можете повторно использовать их имена, не опасаясь, что местные переменные, уже работающие в пределах библиотечного файла, будут в этом случае как-то задеты из вашего главного файла. И напротив, если вы попытаетесь обратиться к местной переменной или подпрограмме библиотечного файла по их именам из вашего главного файла, то получите аварийную остановку программы с сообщением об ошибке "not declared" - "не объявлено".

Имена, объявленные как global, могут действовать и вне того файла, где они объявлены. Но все другие имена действуют только внутри их собственного файла. Это обстоятельство очень полезно при сопровождении или усовершенствовании библиотечного файла или при изучении его работы и использования. Вы можете делать изменения в его внутренних (местных и частных) переменных и местных подпрограммах до тех пор, пока эти изменения не затрагивают работу глобальных переменных и подпрограмм, и вам не нужно будет заботиться о необходимости тестирования других файлов или извещать пользователей библиотеки.

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

     include johns_file.e as john
     include bills_file.e as bill

     john:x += 1
     bill:x += 2
В этом случае глобальная переменная x была объявлена в двух разных файлах, но вы хотите использовать обе переменные в своей программе, не затрагивая исходные файлы. Используя идентификатор пространства имён (префикс, приставку), например, john или bill, вы можете присоединить к x соответствующий префикс, чтобы обозначить, какой именно x вы в данном конкретном случае имеете в виду. Мы иногда говорим в подобных случаях, что john относится к одному пространству имён, в то время как bill относится к другому, отличающемуся от первого, пространству. Вы можете прибавлять префиксы к именам любых переменных, констант или подпрограмм. Вы можете делать это, чтобы погасить реальный конфликт имён, или просто, чтобы сделать вещи более ясными. Имена собственно префиксов имеют местную сцену (в пределах файла). Префикс действует только внутри файла, в котором был объявлен, т.е. в том файле, который содержит соответствующую расширенную команду include. В различных файлах могут быть определены различные префиксы для обозначения одного и того же включенного файла.

Euphoria подталкивает вас к сужению сцены имён. Если бы все имена были автоматически глобальными для всей программы, у вас была бы масса конфликтов имён, особенно в программах, состоящих из файлов, написанных многими разными авторами. Конфликт имён должен вызывать сообщение об ошибке от компилятора, иначе он мог бы вести к очень скрытной ошибке, когда различные части программы встречно и случайно модифицируют одну и ту же переменную, не догадываясь об этом и никак не сигнализируя программисту о творящихся тайных безобразиях. Старайтесь использовать наиболее ограниченную сцену из всех, которые вы можете задать. Делайте переменные частными (только для подпрограммы) везде, где это возможно, а если это невозможно, делайте их местными (только для текущего файла), а не глобальными для целой программы.

Когда интерпретатор Euphoria ищет объявление имени, он в первую очередь проверяет текущую подпрограмму, затем текущий файл, а затем глобальные имена из включенных файлов. Имена, которые "более локальны", скажем так, будут затенять те имена, которые "более глобальны". За границей сцены более локального имени, более глобальное имя вновь выходит из тени и вновь становится видимым.

Объявления констант должны быть вне тела любых подпрограмм. Константы могут быть глобальными или местными (локальными), но не частными.

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

Объявления местных имён на уровне файла, вне тела любых подпрограмм, не должны находиться внутри тела команд циклов (for, while) или тела команды ветвления (if).

Управляющая переменная, используемая в команде цикла for, является особым случаем. Она автоматически объявляется перед началом циклических расчётов, и её сцена ограничена телом команды циклa. Если команда цикла расположена внутри тела подпрограммы, переменная цикла является частной переменной и должна иметь имя, отличающееся от всех других объявленных частных переменных данной подпрограммы (к которым относятся и переменные, объявляемые в списке аргументов). Когда команда цикла находится на уровне файла, вне тела любых подпрограмм, переменная цикла является местной (локальной) переменной и должна иметь имя, отличающееся от всех других уже объявленных местных переменных данного файла. Но вы можете использовать одно и то же имя переменной цикла для различных циклов, если циклы не являются вложенными один в другой. Вам не нужно объявлять тип переменной цикла так, как это делается с другими переменными. Диапазон значений переменной цикла, задаваемый в команде цикла, определяет и все её законные значения, следовательно, любое добавочное задание её типа было бы излишним и поэтому не предусмотрено.


2.4.3 Задание типа переменной

Мы уже видели несколько примеров, касающихся типов переменных, но теперь мы дадим типам более развёрнутое и точное определение.

Объявления переменных как таковые записываются в виде имени типа, сопровождаемого списком имён переменных данного типа, которые мы решили объявить как подлежащие дальнейшему использованию в программе. Например,

        object a

        global integer x, y, z

        procedure fred(sequence q, sequence r)

Типы: object (объект), sequence (ряд), atom (число, атом) и integer (целое) являются предопределёнными. Переменная типа object может принимать любое значение. Те переменные, который объявлены с типом sequence, должны всегда быть рядами. Те переменные, которые объявлены с типом atom, должны всегда быть атомами. А те, которые объявлены с типом integer, должны быть атомами с целочисленной величиной в диапазоне от -1073741824 до +1073741823 включительно. Вы можете выполнять точные целочисленные расчёты с величинами, и выходящими из диапазона integer, вплоть до примерно 15 десятичных знаков, но тогда должны объявлять под них переменные типа atom, а не integer.

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

Примечание о производительности:
Вычисления с использованием переменных, объявленных как integer, будут обычно более быстрыми, чем вычисления с переменными, объявленными как atom. Если ваша машина имеет сопроцессор плавающей точки, Euphoria будет использовать его для работы с атомами, которые непредставимы как integer. Если на вашей машине сопроцессор отсутствует, Euphoria будет вызывать подпрограммы арифметики плавающей точки, которые имеются в интерпретаторе ex.exe (или у Windows). Вы можете заставить ex.exe обходить сопроцессор, установив переменную окружения:
            SET NO87=1
При этом будут использованы сравнительно медленные встроенные подпрограммы, но здесь может быть и некоторое преимущество, если вы, к примеру, обеспокоены наличием ошибки сопроцессора в некоторых ранних версиях микросхем Pentium.

Если предопределённые типы не вполне соответстыуют вашей задаче, вы можете создать свои собственные типы. Всё, что нужно сделать для этого - написать функцию с единственным аргументом, но объявить её как type ... end type вместо function ... end function. Например,

        type hour(integer x)
            return x >= 0 and x <= 23
        end type

        hour h1, h2

        h1 = 10      -- ok
        h2 = 25      -- ОШИБКА! Программа прерывается с сообщением

Переменным h1 и h2, объявленным с типом hour (час), может быть присвоена только целочисленная величина в диапазоне от 0 до 23 включительно. После каждого присваивания h1 или h2 интерпретатор будет вызывать тип hour(), подавая новую величину. Величина сначала будет проверена на соответствие типу integer (объявлена как "integer x"). Если соответствует, то будет вычисляться выражение, подаваемое в команду return, чтобы проверить значение x (т.е. новую величину h1 или h2). Если hour() выдаёт значение "истина", исполнение программы продолжается. Если hour() выдаёт значение "ложь", программа прерывается с соответствующим диагностическим сообщением.

Тип "hour" может использоваться также для объявления аргументов подпрограммы:

        procedure set_time(hour h)

Процедура set_time() может быть вызвана только с подходящей величиной аргумента h, иначе программа будет прервана с сообщением.

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

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

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

Проверка типов может быть выключена или включена между подпрограммами с помощью специальных команд with type_check (с проверкой типа) или without type_check (без проверки типа). По умолчанию проверка типов включена при старте программы.

Примечания о проверках производительности:
Сравнивая скорость программы Euphoria со скоростью программ, написанных на других языках, в начале программы Euphoria необходимо написать without type_check. Эта команда даёт интерпретатору Euphoria разрешение пропускать проверки типов во время исполнения программы, чтобы сэкономить некоторое время. Все остальные проверки будут при этом выполняться, т.е. по индексам, инициализации и т.п. Даже при выключенной вами проверке типов интерпретатор Euphoria оставляет за собой право делать эту проверку в стратегически важных местах, так как она на деле позволяет вашей программе исполняться быстрее во многих случаях. Следовательно, вы можете получить отказ по проверке типа, даже когда проверку выключаете. Включена проверка типов или выключена, вы никогда не получите исключительную ситуацию машинного уровня. У вас всегда будет внятное сообщение от Euphoria, если что-то пошло неправильным путём. (Конечно, и здесь можно нарваться на машинное исключение, например, когда вы делаете poke (размещаете данные непосредственно в памяти), или вызываете подпрограммы, написанные на C или в машинном коде. Но это уже совсем другая история - в этих случаях Euphoria просто уважает ваше неотъемлемое право давать вашей машине любые ваши команды.)

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

Но во многих программах забота о каких-то новых типах данных не приносит значительных преимуществ, и вам может вполне хватить четырёх имеющихся предопределённых типов. В отличие от других языков, механизм типов Euphoria даёт вам более свободный выбор. А с типом object вы можете и не вспоминать о наличии остальных трёх.

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

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

Анекдот 1:
Перенеся большую программу C на Euphoria, мы обнаружили ряд скрывавшихся ранее ошибок. Хотя данная программа C и заслужила большое доверие как якобы тотально "корректная", мы нашли: ситуацию, где считывалась неинициализированная переменная; место, где элемент номер "-1" одного из массивов беспрепятственно записывался и считывался; ситуацию, где что-то выводилось как раз мимо экрана. Эти проблемы вели к ошибкам, которые были не слишком заметны для не очень внимательного наблюдателя, и код C успешно прошёл тестирование.

Анекдот 2:
Алгоритм Quick Sort, представленный на странице 117 "Написания эффективных программ" Джона Бентли, имеет ошибку индексирования! Этот алгоритм будет иногда считывать элемент из памяти непосредственно перед началом сортируемого массива, и будет считывать из памяти элемент непосредственно после конца массива. Хотя и читается какой-то мусор, оставшийся от другой программы, алгоритм всё ещё работает - вероятно, поэтому ошибка никогда и не была обнаружена. Но что, если бы по тем адресам вне массива не было никакой (виртуальной) памяти? Бентли позже модифицировал алгоритм так, что эта ошибка уходит -- но он представлял и раннюю версию как правильную. Даже экспертам нужна проверка правильности индексирования!

Примечания о производительности:
Когда программист широко применяет свои собственные типы, их проверка интерпретатором добавляет ко времени исполнения программы всего 20..40 процентов. Оставляйте проверку включенной, если вам не нужна действительная экстра-скорость. Вы можете также предусмотреть её выключение только для нескольких особенно сильно загруженных вычислениями подпрограмм. Профилирование может помочь в принятии правильного решения.


2.5 Команды

В Euphoria предусмотрены следующие виды исполняемых команд:

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


2.5.1 команда присваивания

В Euphoria команда присваивания служит для придания величины, полученной при вычислении выражения, простой переменной или элементу ряда, или отрезку ряда, то есть,

        x = a + b

        y[i] = y[i] + 1

        y[i..j] = {1, 2, 3}

По этой команде предыдущая величина переменной или элемента(ов) ряда, или отрезка ряда аннулируется, а новая вступает в действие. Например, предположим, что x был 1000-элементным рядом, который мы инициализировали следующим образом:

        object x

        x = repeat(0, 1000)  -- ряд, состоящий из 1000 нулей
а затем мы присвоили x значение атома:
        x = 7
Такое действие полностью законно, так как мы объявили x как object. Предыдущая величина x, а именно, 1000-элементный ряд, просто исчезнет. Одновременно то пространство, которое занимал в памяти 1000-элементный ряд, будет автоматически возвращено в резерв вашей программы, благодаря динамическому распределению памяти, работающему в Euphoria.

Заметьте, что символ равенства '=' используется и для записи команды, и как оператор при проверке равенства в выражениях. Но здесь никогда не возникает путаница, так как присваивание в Euphoria является только командой, и оно не может быть использовано в составе выражений (как это бывает в других языках).


Присваивание с операцией

Euphoria имеет также некоторые дополнительные формы команды присваивания.

Чтобы сократить код и сделать его чуть-чуть красивее, вы можете объединить символ присваивания с одним из операторов:
      +   -   /   *   &

Например, вместо того, чтобы писать:

      mylongvarname = mylongvarname + 1
вы можете записать:
      mylongvarname += 1
вместо записи:
      galaxy[q_row][q_col][q_size] = galaxy[q_row][q_col][q_size] * 10
будет работать:
      galaxy[q_row][q_col][q_size] *= 10
а вместо:
      accounts[start..finish] = accounts[start..finish] / 10
просто:
      accounts[start..finish] /= 10

В общем случае, везде, где вы имеете присваивание в форме:

      левая_часть = левая_часть оп выражение
вы можете записать:
      левая_часть оп= выражение
где оп может быть одним из:    +   -   *   /   &

Когда левая_часть содержит несколько индексирований/сечений, форма оп= будет исполняться обычно быстрее, чем длинная форма. Когда вы привыкнете к короткой форме, вы сможете заметить, что форму оп= легче читать, так как здесь нет необходимости визуально сравнивать левую_часть с её же копией, расположенной в команде справа.


2.5.2 вызов подпрограммы

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

        plot(x, 23)

2.5.3 команда if (если)

Команда ветвления if (если) проверяет некоторое условие, убеждаясь в его истинности (не-нуль) или ложности (0), а затем обеспечивает переход к выполнению соответствующих тех или иных серий других команд. В команде if могут содержаться дополнительные пункты проверки elsif (а_если) и else (иначе), то есть,

        if a < b then
            x = 1
        end if


        if a = 9 and find(0, s) then
            x = 4
            y = 5
        else
            z = 8
        end if


        if char = 'a' then
            x = 1
        elsif char = 'b' or char = 'B' then
            x = 2
        elsif char = 'c' then
            x = 3
        else
            x = -1
        end if

Обратите внимание, что elsif хотя и является сокращением от else if, но это сокращение делает текст более ясным, так как отпадает необходимость в лишнем end if, которое пришлось бы ставить при несокращенной форме. А так имеется только одна закрывающая "скобка" end if для всего блока команды if, даже когда в нём содержится множество пунктов elsif.

Выражения, входящие в пункты if и elsif, проверяются с использованием укороченной проверки истинности.


2.5.4 команда while (пока)

Команда while (пока) проверяет сопровождающее её выражение на истинность, и пока выражение истинно, циклически выполняет те команды, которые записаны далее в её теле, то есть,

        while x > 0 do
            a = a * 2
            x = x - 1
        end while

укороченная проверка истинности

Когда выражение, проверяемое командами if, elsif или while содержит операторы and или or, то проводятся укороченные вычисления. Например,

        if a < 0 and b > 0 then ...
если a < 0 ложно, то Euphoria не будет беспокоиться о второй проверке (не больше ли b чем 0). Будет решено, что общий результат уже и без того соответствует лжи. Аналогично,
        if a < 0 or b > 0 then ...
если a < 0 истинно, то Euphoria немедленно решит, что общий результат соответствует истине и без проверки величины b.

В общем, каждый раз, когда мы ставим условие в форме:

        A and B
где A и B могут быть двумя любыми выражениями, Euphoria завершит расчёт, если A ложно, и немедленно станет считать общий результат ложью, даже не взглянув на выражение B.

Аналогично, в случае:

        A or B
когда A истинно, Euphoria пропустит вычисление выражения B и учтёт результат как истину.

Если выражение B содержит вызов функции, то из-за возможности наличия у функции побочных эффектов, т.е. и других действий, кроме простой выдачи величины, вы получите предупреждение во время компиляции. Более старые версии Euphoria (до 2.1) не имели укороченной проверки, поэтому возможно, что некоторый старый код будет работать неправильно, хотя поиск в архивах Euphoria и не выявил какие-либо программы, зависящие от побочных эффектов в этом смысле.

Выражение, B в данном случае, может содержать какие-либо дефекты кода, которые во время исполнения программы могли бы спровоцировать её аварийную остановку. Но если Euphoria пропускает вычисление B, ошибка не будет обнаружена, например,

        if x != 0 and 1/x > 10 then  -- деление на нуль пропущено

        while 1 or {1,2,3,4,5} do    -- пропущен незаконный результат с рядом
Выражение B могло бы даже содержать неинициализированную переменную, индекс вне границ и другие недопустимые вещи.

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

        if atom(x) or length(x)=1 then
Без укороченной проверки у вас была бы проблема, когда x является атомом, так как длина для атомов не определена. Но с укороченной проверкой length(x) будет проверяться, только когда x является рядом. Аналогично:
        -- найти 'a' или 'A' в s
        i = 1
        while i <= length(s) and s[i] != 'a' and s[i] != 'A' do
             i += 1
        end while
В данном цикле переменная i может стать больше length(s). Без укороченной проверки вас поджидала бы ошибка индексирования, когда s[i] вычисляется в самом последнем цикле. Но с укороченной проверкой циклические вычисления будут немедленно прерваны, как только выражение i <= length(s) станет ложным. Euphoria не будет вычислять s[i] != 'a' и не будет вычислять s[i] != 'A'. И вы, тем самым, будете застрахованы от ошибки индексирования.

Укороченная проверка and и or имеет место только в командах if, elsif и while. Она не используется в других контекстах. Например, команда присваивания:

        x = 1 or {1,2,3,4,5}  -- x будет присвоено значение {1,1,1,1,1}
Если бы укороченная проверка была применена здесь, мы бы получили x равным 1, а интерпретатор даже не взглянул бы на {1,2,3,4,5}. Это было бы неправильным решением. Укороченная проверка может использоваться в командах if/elsif/while, поскольку там нас заботит лишь общий результат в виде да/нет, а этим командам только и нужен именно атом, как результат вычисления выражения для проверяемого условия.


2.5.5 команда for (для, от)

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

        for i = 1 to 10 do
            ? i   -- ? - это короткая форма для print()
        end for

        -- дробные числа также разрешены
        for i = 10.0 to 20.5 by 0.3 do
            for j = 20 to 10 by -2 do    -- счёт вниз
                ? {i, j}
            end for
        end for

Переменная цикла объявляется автоматически и существует до конца циклических вычислений, предусмотренных данным циклом. Вне тела команды цикла переменная цикла не имеет величины и даже не может считаться объявленной. Если вам необходима её последняя величина, присвойте её величину некоторой заранее объявленной обычной переменной перед выходом из цикла. Компилятор не позволяет никаких присваиваний переменной цикла. Начальная величина, конечная величина и величина шага все должны быть атомами. Если величина шага не указана, считается, что шаг равен +1. Конечная величина и величина шага устанавливаются при входе в цикл и во время цикла никак не могут быть изменены. См. также о сцене переменной цикла в разделе 2.4.2 Сцена (область видимости).


2.5.6 команда return (выдать, вернуться)

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

        return

        return {50, "FRED", {}}

2.5.7 команда exit (выйти)

Команда exit может использоваться в цикле while или в цикле for. Она вызывает немедленное прерывание цикла с передачей управления первой команде, следующей после тела цикла. То есть,

        for i = 1 to 100 do
            if a[i] = x then
                location = i
                exit
            end if
        end for

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

        constant TRUE = 1

        while TRUE do
            ...
            if some_condition then
                exit
            end if
            ...
        end while
То есть, это "бесконечный" цикл while, который прерывается командой exit в некоторой характерной точке в теле цикла.
Примечания о производительности:
В Euphoria этот бесконечный тип цикла оптимизирован. Во время прогона программы никакие повторные проверки истинности выражения в команде while уже не выполняются, а совершается простой безусловный переход от end while обратно к первой команде внутри тела цикла.

Если вам случится попасть в настоящий бесконечный цикл с интерпретатором ex.exe, когда не выполняются операции ввода/вывода, знайте, что простого пути остановить этот цикл не существует. Вы должны будете одновременно нажать клавиши Control-Alt-Delete, чтобы перезагрузиться под DOS, или прервать свой сеанс DOS под Windows. Если программа имела файлы, открытые на запись, будет полезно после перезагрузки запустить scandisk, чтобы проверить исправность своей файловой системы. Только если ваша программа ожидает ввода с клавиатуры, комбинация Control-c может прервать её исполнение (если вами не была выдана команда allow_break(0)).

С интерпретаторами exw.exe или exu комбинация control-c всегда работает и немедленно прервёт вашу программу, если у вас есть в этом необходимость.



2.6 Специальные команды высшего уровня

Перед тем, как исполнять любую из ваших команд, интерфейсный блок интерпретатора Euphoria быстро читает всю вашу программу полностью от начала и до конца. Все ваши команды проверяются синтаксически и преобразуются в низкоуровневый код на промежуточном языке (IL). Затем исполнительный блок интерпретатора немедленно приступает к исполнению кода IL, размещённого в оперативной памяти. Биндер/шроудер записывает код IL в файл .il на диск для последующих немедленных исполнений с отдельным исполнительным блоком. Транслятор преобразует код IL в код C и записывает программу C на диск. Во всех этих трёх методах преобразования текста исходного кода Euphoria используется один и тот же интерфейсный блок (написанный на Euphoria).

Если ваша программа содержит только объявления подпрограмм и переменных, но не имеет команд высшего уровня, то после её запуска не произойдёт ровно ничего (кроме, конечно, проверки синтаксиса и формирования кода IL). Поэтому в вашей программе должна быть хотя бы одна команда высшего уровня, например, вызывающая вашу главную подпрограмму на исполнение (см. 1.1 Пример программы). Возможны программы, в которых нет ничего, кроме исполняемых команд высшего уровня, без каких бы то ни было подпрограмм. Например, вы можете использовать Euphoria как простой калькулятор, записав просто несколько команд print() (или ?) с выражениями в файл, и затем исполняя его.

Как мы уже видели, на высшем уровне вы можете использовать почти любую команду Euphoria, включая циклы for, циклы while, команды if и так далее (но не return). Высший уровень - это уровень вашего главного файла на тех участках его текста, которые не относятся к определению подпрограмм, то есть вне тела любых ваших функций или процедур. Но имеется несколько специальных команд, которые могут появиться в вашей программе только на высшем уровне, это:

  • include
  • with / without


2.6.1 include (учесть, включить)

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

include filename
По этой команде считывается (компилируется) необходимый вам файл исходного кода Euphoria, имеющий некоторое имя filename.

Несколько примеров:

                   include graphics.e
                   include \mylib\myroutines.e

Любой код, расположенный на высшем уровне включаемого файла, будет исполнен.

Любые глобальные (global) имена, которые уже были объявлены и определены в вашем главном файле перед включением, будут видны и во включенном файле.

Примечание: Но только те из имён, которые объявлены и определены во включаемом файле с ключевым словом global, будут видны и доступны в той части программы, которая следует за командой include.

Если задано абсолютное имя файла filename, Euphoria просто откроет файл и начнёт в нём проверку правописания кода. Если задано относительное имя файла filename, Euphoria будет пытаться найти и открыть файл относительно следующих каталогов (директорий) в следующем порядке:

  1. Каталог, содержащий текущий файл исходного кода, т.е. файл, который содержит отрабатываемую в данный момент команду include ...

  2. Каталог, содержащий главный файл, заданный в командной строке интерпретатора, транслятора или биндера (шроудера) ...

  3. Если вы задали на своей машине переменную окружения EUINC, Euphoria будет проверять каждый из каталогов, перечисленных в EUINC (слева направо). Переменная EUINC должна задавать список каталогов, отделённых один от другого точкой с запятой (двоеточием на Linux/FreeBSD), подобно тому, как это делается для переменной PATH. Переменная EUINC может быть добавлена в набор переменных окружения вашей операционной системы Linux/FreeBSD или DOS/Windows. (Через Control Panel/Performance & Maintenance/System/Advanced на XP, или AUTOEXEC.BAT на более старых версиях Windows), то есть,
          SET EUINC=C:\EU\MYFILES;C:\EU\WIN32LIB
    Переменная EUINC позволяет вам организовать свою систему библиотек Euphoria в соответствии с потребностями конкретных программных проектов, не забивая многочисленными нестандартными библиотеками официальный каталог euphoria\include.

  4. И, наконец, если заданный в команде include файл всё ещё не найден, Euphoria проверит каталог euphoria\include. Этот каталог содержит стандартные официальные включаемые файлы (библиотеки) Euphoria. Переменная окружения EUDIR подскажет Euphoria, где искать (и под каким именем вы назначили) главный каталог, содержащий систему Euphoria, на ваших дисках (это может быть, если вы хотите, не euphoria, а eu или RDS_EU, или ещё какой-то каталог, который вам более удобен).

Включаемый файл может в свою очередь также включать другие файлы. Фактически вы можете использовать до 30 последовательных уровней "вложения" включаемых файлов.

Включаемый файл обычно имеет расширение .e, но иногда это может быть .ew или .eu (когда файл предназначен именно для платформы Windows или Linux/FreeBSD). Так просто принято, но это не обязательное требование.

Если filename (или путь) содержат в имени символы пробела, вы должны заключить filename в двойные кавычки. В других случаях кавычки необязательны. Не забывайте сдваивать косую черту. Например:

                   include "c:\\program files\\myfile.e"

Если не задан специальный идентификатор пространства имён (см. ниже), команда include будет безмолвно игнорировать те полностью идентичные файлы, которые уже однажды были в программу включены.

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

Вторая форма команды include записывается следующим образом:
include filename as namespace_identifier

Ко всем свойствам обычной include во второй форме прибавляется возможность объявления префикса пространства имён, который может быть прибавлен перед глобальными именами, объявленными в данном включаемом файле, когда к ним производится обращение из текущего главного файла. Эти префиксы могут быть необходимыми, чтобы исключить двусмысленности с некоторыми именами, или вы находите, что ваш код становится с ними более ясным. См. раздел 2.4.2 Сцена (область видимости), чтобы вспомнить другие детали об этих префиксах.


2.6.2 with / without (с / без)

Эти специальные команды (метакоманды) управляют способами, которыми Euphoria транслирует вашу программу во внутреннюю форму (IL). Они не меняют общую логику вашей программы, но влияют на диагностическую информацию, которую вы получаете при исполнении вашей программы. См. также раздел 3. Отладка и профилирование, там есть дополнительные сведения об этих командах.

with
Данная команда включает исполнение программы в один из режимов: profile (профиль), profile_time (профиль_времени), trace (трасса), warning (предупреждение) или type_check (проверка_типа) . Режимы warning и type_check включены по умолчанию, тогда как profile, profile_time и trace по умолчанию выключены.

Все предупреждения, скопившиеся во время исполнения вашей программы, выводятся на экран после окончания исполнения кода программы. Предупреждения указывают на очень незначительные погрешности в коде и никогда не прерывают выполнение программы.

without
Данная команда выключает один из отмеченных выше режимов.

Имеется также редкий в настоящее время режим with, который обозначается кодовым числом, следующим непосредственно за словом with. В предыдущих версиях Euphoria эти кодовые числа использовались исключительно RDS для сброса счётчика числа команд в редакции "Public Domain".

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

Во включенном файле наследуются заданные установки with/without по состоянию на тот момент, в той точке, где записана команда include. Во включенном файле эти установки могут измениться, но они вернутся к своему оригинальному состоянию по окончании исполнения включенного файла. Например, библиотечный файл может выключить предупреждения для себя самого и (в первый момент) для всех библиотек, которые он сам использует, но он не может выключить таким же образом предупреждения, если они включены и действуют в главном файле.

 

... продолжение
3. Отладка и профилирование