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


2.1 Объекты


2.1.1 Атомы и ряды

Все объекты, содержащие данные, в Euphoria могут быть атомами или рядами. Атом это единственная численная величина. Ряд является набором численных величин.

Объекты, входящие в ряд, могут быть произвольной смесью атомов или рядов. Ряд представляется списком объектов, заключенным в фигурные скобки, объекты разделяются запятыми. Атомы могут иметь любую целочисленную величину или величину двойной точности с плавающей точкой. Диапазон значений величин лежит в пределах от приблизительно -1e300 (минус единица на 10 в степени 300) до +1e300 с 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}
        {}                        -- пустой ряд, 0 элементов

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

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

Для написания шестнадцатиричных чисел разрешены только прописные буквы
A, B, C, D, E, F.

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

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

Слова "Hierarchical Objects", часть акронима Euphoria, отражают иерархическую природу вложенных рядов. Здесь не должно быть путаницы с "class hierarchies" некоторых объектно-ориентированных языков.

Почему мы назвали их атомами? Почему не просто "числами"? Что ж, атомы действительно являются просто числами, но мы хотели подчеркнуть их дальнейшую неделимость в ряд. Конечно же, в мире физики атомы давным-давно разделены на более мелкие части, но в 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        новая строка
        \r        возврат каретки
        \t        табуляция
        \\        обратный солидус
        \"        двойная кавычка
        \'        одинарная кавычка

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


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

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

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

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

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

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



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 (ложь)
        -4.4 > -4.3 -- -4.4 больше чем -4.3 (ложь)
        8 <= 7      -- 8 меньше или равно 7 (ложь)
        4 >= 4      -- 4 больше или равно 4 (истина)
        1 = 10      -- 1 равно 10           (ложь)
        8.7 != 8.8  -- 8.7 не равно 8.8     (истина)

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


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

Логические операторы and, or, xor, и not используются, чтобы задать "истинность" выражения т.е.

        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 (истина)

Вы можете применять эти операторы к числам, отличающимся от 1 или 0. Правило таково: нуль означает ложь, не-нуль означает истину. Итак, например:

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

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

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


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

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

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

Вычисление результата, который слишком велик (т.е. находится вне пределов от -1e300 до +1e300), будет давать один из специальных атомов +бесконечность или -бесконечность. Эти результаты будут отображаться как inf или -inf, когда вы станете их выводить на экран или в файл. Возможно также появление результатов nan или -nan. "nan" означает "not a number - не число", т.е. неопределенная величина (такая как inf деленное на inf). Эти специальные величины введены стандартом 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}    -- {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}
        -- обратите внимание, что первое '=' есть присваивание, а
        -- второе '=' есть оператор отношения, который проверяет
        -- эквивалентность

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 = {
                    {"John","Smith"},
                    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.

Структуры данных 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] также разрешено, эта форма выдает ряд длины-0, {}. Если 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], не разрешены.


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), так как [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(s)

length() length() выдает вам длину ряда s. Длина это число элементов в s. Некоторые из этих элементов могут быть рядами, которые содержат свои собственные элементы, но length дает подсчет только для самого верхнего уровня. Если вы попытаетесь определить длину атома, это вызовет сообщение об ошибке, т.е.

        length({5,6,7})             -- 3
        length({1, {5,5,5}, 2, 3})  -- 4 (не 6!)
        length({})                  -- 0
        length(5)                   -- ошибка!

repeat(item, count)

repeat() соэдает ряд, который состоит из элемента item, повторенного count раз, т.е.

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

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


append(s, item) / prepend(s, item)

append() создает новый ряд, добавляя элемент item к концу ряда s. prepend() создает новый ряд, прибавляя элемент перед началом исходного ряда 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 не копирует большие объекты, а обходится их пометками.

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

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

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

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

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

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

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

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



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 вы должны для получения такого списка установить странные механизмы, которые настолько сложны, что средний программист не сможет обойтись без штудирования документации или консультаций с местным гуру.

В процедуру подается копия величины каждого из параметров. Эти копии являются формальными переменными со значением параметров и с их именем. Внутри процедуры они могут претерпевать изменения, но эти изменения не коснутся тех фактических исходных переменных, которые поданы как параметры (в отличие от QB4.5, например. Прим.перев.)

Замечание о производительности:
Интерпретатор на деле не создает копий рядов или чисел с плавающей точкой, пока можно обойтись без этих копий. Например,
            y = {1,2,3,4,5,6,7,8.5,"ABC"}
            x = y
Оператор x = y не копирует y в x, чтобы получить еще один ряд. И x, и y будут просто "указывать" на один и тот же ряд. Если позже мы выполним x[3] = 9, только тогда отдельный ряд будет создан в памяти для x (хотя все еще останется только один общий экземпляр 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}

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


Типы

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


Переменные

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

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

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

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


Константы

Константы (постоянные) это переменные (!), которым присваиваются некоторые изначальные величины. Эти изначальные величины затем не могут быть изменены, т.е.

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

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

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


2.4.2 Сцена

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

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

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

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

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

Идентификаторы, помеченные как 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 подталкивает программиста к сужению сцены идентификаторов. Если бы все идентификаторы были автоматически глобальными во всей программе, это порождало бы тенденцию к появлению конфликтов имен в больших программах, особенно, когда различные части программы создаются другими программистами. Конфликты в именах могут приводить к простым сообщениям об ошибке наименования во время компиляции, но они могут вести и к скрытой ошибке, когда различные части программы встречно модифицируют одну и ту же переменную, даже не давая программисту повода догадаться об этом. Старайтесь использовать такую узкую сцену для переменных, которая еще не становится обременительной. Делайте переменные частными для одной подпрограммы везде, где это возможно, а где это невозможно, делайте их местными для файла, прежде чем сделать global - общими для всей своей программы, начиная от точки объявления.

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

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

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

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

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


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 включительно. Вы можете выполнять точные расчеты и с более значительными целочисленными величинами, до 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      -- все в порядке
        h2 = 25      -- ошибка! программа прерывается с сообщением

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

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

        procedure set_time(hour h)

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

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

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

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

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

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

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

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

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

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

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

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


2.5 Операторы

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

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


2.5.1 Оператор присваивания

Оператор присваивания придает заданную величину простой переменной, индексу или отрезку переменной, т.е.

        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 является только оператором, оно не может быть использовано как выражение (в отличие от C).


Присваивание с оператором

В 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

В общем, вместо присваивания в форме:

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

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


2.5.2 Оператор вызова подпрограммы

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

        plot(x, 23)

2.5.3 Оператор if

Оператор if проверяет условие, чтобы выявить его истинность (не-нуль) или ложность (0), а затем продолжить выполнение условных серий операторов. В этом операторе могут быть также предложения 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 нуля или нет. Она примет, что общий результат ложен. Аналогично,
        if a < 0 or b > 0 then ...
если a < 0 истинно, Euphoria немедленно решит, что общий результат истиннен, без дальнейшей проверки величины b.

В общем, если имеется условие в форме:

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

Аналогично с:

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

Если выражение B содержит вызов функции, и эта функция имеет возможные сторонние эффекты, т.е. может делать больше, чем простая выдача результата, вы получите предупреждение времени компиляции. Более старые версии (до-2.1) Euphoria не могли использовать укороченную процедуру, и поэтому возможно, что некоторый старый код не будет больше работать правильно, хотя поиск в архивах 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 оптимизирован. Во время прогона в операторе while не выполняется никаких проверок. По сути это простой безусловный переход от end while обратно к первому оператору внутри тела цикла.

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

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



2.6 Специальные операторы верхнего уровня

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

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

  • include
  • with / without.


2.6.1 Оператор include

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

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

Любой код верхнего уровня во включенном файле будет исполнен.

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

Примечание. Только те идентификаторы, которые определены в файле filename как global, будут видны (доступны) в той части вашей программы, которая следует за оператором include filename.

Если задано абсолютное имя filename, Euphoria будет использовать его. Когда же задано относительное имя filename, Euphoria сначала просмотрит текущий каталог, в котором находится главный файл, указанный в строке запуска интерпретатора ex (или exw или exu). Если файла там нет, но вы определили новую переменную окружения, EUINC, будет просмотрен каждый каталог из перечисленных в EUINC (слева направо). И окончательно, если файл все еще не найден, будет выполнен поиск в каталоге euphoria\include. Этот последний каталог содержит стандартные библиотеки Euphoria. Переменная окружения EUDIR говорит интерпретаторам ex.exe/exw.exe/exu, где искать ваш каталог euphoria. А EUINC является списком каталогов, отделенных один от другого точкой с запятой (двоеточием под Linux), похожим по форме на список вашей старой хорошей знакомой, переменной PATH. Новая переменная EUINC может быть введена командой из вашего файла AUTOEXEC.BAT, например,
SET EUINC=C:\EU\MYFILES;C:\EU\WIN32LIB
С этой переменной вы сможете организовать ваши библиотеки в соответствии с потребностями ваших программ и избежать добавления бесчисленных не относящихся к делу файлов в ваш каталог euphoria\include.

Библиотечные файлы могут сами включать другие библиотечные файлы и так далее. Глубина вложения библиотечных файлов может достигать 10 уровней.

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

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

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

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

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


2.6.2 Операторы with / without

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

with
Включает один из режимов profile, profile_time, trace, warning или type_check . Режимы warning и type_check по умолчанию включены, а profile, profile_time и trace по умолчанию выключены.

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

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

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

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

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

 

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