> G:= [59, 64, 39, 44, 10, 17]: add(G[k]*`if`(type(G[k], 'odd'), 1, 0), k=1..nops(G)); ⇒ 115
> mul(G[k]*`if`(type(G[k], 'even'), 1, `if`(G[k]=0, 1, 1/G[k])), k=1..nops(G)); ⇒ 28160
> F:=[10,GS,17]: (seq(F[k]*`if`(type(F[k],‘symbol’),1,0),k=1..nops(F)))(x,y,z); ⇒ 0, GS(x,y,z), 0 > (seq(`if`(type(F[k], 'symbol'), true, false) and F[k], k=1..nops(F))); ⇒ false, GS, false
Первые два примера фрагмента иллюстрируют применение управляющей if-структуры для обеспечения дифференцировки выбора слагаемых и сомножителей внутри функций add и mul. Тогда как третий и четвертый примеры формируют последовательности вызовов функций на основе результатов их тестирования. Данные приемы могут оказаться достаточно полезным средством в практическом программировании в среде языка Maple различного рода циклических вычислительных конструкций.
В циклических конструкциях типа `for k in n$n=a..b...` не допускается отождествления идентификаторов k и n, не распознаваемого синтаксически, но приводящего к ошибкам выполнения конструкции. Более того, в общем случае нельзя отождествлять в единой конструкции переменные цикла и суммирования/произведения, например:
> h:= 0: for k to 180 do h:= h + sum(1, k = 1 .. 64) end do; h; ⇒ 0
Error, (in sum) summation variable previously assigned, second argument evaluates to 1 = 1 .. 64
> h:= 0: for k to 180 do h:= h + sum(1, 'k' = 1 .. 64) end do: h; ⇒ 11520
> h:= 0: for k to 180 do h:= h + product(1, k = 1 .. 64) end do: h; ⇒ 0
Error, (in product) product variable previously assigned, second argument evaluates to 1 = 1 .. 64 > h:= 0: for k to 180 do h:= h + product(2, 'k' = 1 .. 64) end do: h;
3320413933267719290880
> h:= 0: for k in ['k' $ 'k'=1..64] do h:= h + k end do: h; ⇒ 2080
> h:= 0: for k in [k $ 'k'=1 .. 64] do h:= h + k end do: h; ⇒ 4096
> h:= 0: for k in [k $ k=1 .. 64] do h:= h + k end do: h; ⇒ 0
Error, wrong number (or type) of parameters in function $
Вместе с тем, как иллюстрирует фрагмент, корректность выполняется при кодировании переменной суммирования/произведения в невычисленном формате. Более того, три последних примера фрагмента иллюстрируют как допустимость, так и корректность использования общей переменной внешнего цикла и циклической $-конструкции, но при условии использования последней в невычисленном формате. Данное обстоятельство определяется соглашениями Maple-языка по использованию глобальных и локальных переменных, детально рассматриваемых в следующей главе книги, посвященной процедурным объектам языка.
Дополнительно к сказанному, следует иметь в виду весьма существенное отличие в выполнении seq-функции и логически эквивалентного ей $-оператора. Если функция seq носит достаточно универсальный характер, то $-оператор более ограничен, в целом ряде случаев определяя некорректную операцию, что весьма наглядно иллюстрирует следующий простой фрагмент применения обоих средств Maple-языка пакета:
> S:="aqwertyuopsdfghjkzxc": R:=convert(S,'bytes'): convert([R[k]],'bytes') $ k=1..nops(R); Error, byte list must contain only integers
> cat(seq(convert([R[k]], 'bytes'), k = 1 .. nops(R))); ⇒ "aqwertyuopsdfghjkzxc"
> X,Y:=99,95: seq(H(k),k=`if`(X<90,42,95)..`if`(Y>89,99,99)); ⇒ H(95),H(96),H(97),H(98),H(99)
Таким образом, оба, на первый взгляд, эквивалентные средства формирования последовательностных структур следует применять весьма осмотрительно, по возможности отдавая предпочтение первому, как наиболее универсальному. В этом отношении для seqфункции имеют место (в ряде случаев весьма полезные) следующие соотношения: seq(A(k), k = [B(x)]) ≡ seq(A(k), k = {B(x)}) ≡ (A@B)(x) ≡ A(B(x)) seq(A(k), k = x) ≡ seq(A(k), k = B(x)) ≡ A(x)
При использовании `if`-функции для организации выхода из циклических конструкций рекомендуется проявлять внимательность, ибо возвращаемое функцией break-значение не воспринимается в качестве управляющего слова Maple-языка пакета, например:
> R:= 3: do R:= R - 1; if R= 0 then break end if end do: R; ⇒ 0
> R:= 3: do R:= R-1; `if`(R=0, `break`, NULL) end do: R; ⇒ 0
Error, invalid expression
Первый пример фрагмента иллюстрирует успешный выход из do-цикла по достижении R-переменной нулевого значения и удовлетворения логического условия if-предложения. Тогда как второй пример показывает невозможность выхода из идентичного do-цикла на основе `if`-функции, возвращающей на значении R=0 break-значение, не воспринимаемое в качестве управляющего слова. При этом, если в Maple 7–10 инициируется ошибочная ситуация, то еще в Maple 6 второй пример, не вызывая ошибочной ситуации, выполняет бесконечный цикл, требуя прекращения вычислений по stop-кнопке GUI. В случае использования вместо break функций done, quit и stop выполняется бесконечный цикл, требуя прекращения вычислений по stop-кнопке GUI.
Циклические вычислительные конструкции можно определять и на основе функций {select, remove}, имеющих следующий единый формат кодирования:
{select|remove}(<ЛФ>, <Выражение> {, <Параметры>})
Результатом вызова select-функции является объект того же типа, что и ее второй фактический аргумент, но содержащий только те операнды выражения, на которых логическая функция (ЛФ) возвращает true-значение. Третий необязательный аргумент функции определяет дополнительные параметры, передаваемые ЛФ. В качестве выражения могут выступать список, множество, сумма, произведение либо произвольная функция. Функция remove является обратной к select-функции. Следующий простой фрагмент иллюстрирует применение функций select и remove для циклических вычислений:
> select(issqr, [seq(k, k= 1 .. 350)]);
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324]
> ЛФ:= x ->`if`(x >= 42 and x <= 99, true, false): select(ЛФ, [seq(k, k = 1 .. 64)]);
[42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64]
> remove(ЛФ, [seq(k, k = 1 .. 64)]); ⇒ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41]
Приведенный фрагмент достаточно прозрачен и особых пояснений не требует.
В целом ряде случаев работы с данными индексированного типа {array, Array и др.} возникает необходимость динамической генерации вложенных циклических конструкций, когда уровень циклической вложенности заранее неизвестен и вычисляется программно. В этом случае может оказаться весьма полезной процедура SEQ [41,103]. Процедура обеспечивает динамическую генерацию seq-конструкций следующего типа:
seq(expr[k], k = a .. b) и seq(expr[k], k = V)
Следующий пример иллюстрирует применение SEQ-процедуры:
> SG:= Array(1..2, 1..3, 1..4): SEQ([k, 1..2], [j, {1, 2, 3}], [h, 2..3], "assign('SG'[k,j,h]=k+j+h)"); ArrayElems(SG);
{(1, 1, 2) = 4, (1, 1, 3) = 5, (1, 2, 2) = 5, (1, 2, 3) = 6, (1, 3, 2) = 6, (1, 3, 3) = 7, (2, 1, 2) = 5,
(2, 1, 3) = 6, (2, 2, 2) = 6, (2, 2, 3) = 7, (2, 3, 2) = 7, (2, 3, 3) = 8}
Используемый процедурой прием может быть использован для расширения других циклических конструкций [41,103]. Детальнее группа функциональных средств, объединенных управляющей структурой циклического типа, на семантическом уровне рассматривалась выше. Здесь же на нее было акцентировано внимание именно в связи с вопросом механизма организации управляющих структур Maple-языка.
На основе рассмотренных управляющих структур следования, ветвления и цикла, поддерживаемых Maple-языком, пользователь в сочетании с его функциональными средствами получает достаточно развитый инструмент для создания собственных программных средств, ориентированных на его конкретные приложения. В этом отношении поддерживаемый языком механизм процедур позволяет создавать его приложения не только структурированными в указанном выше смысле, но и более модульными. В следующей главе рассматриваются процедурные и модульные объекты Maple-языка, что позволит с большим пониманием освоить и использовать программную среду пакета для создания приложений в различных областях.
Сложность в общем случае является достаточно интуитивно-субъективным понятием и его исследование представляет весьма трудную фундаментальную проблему современного естествознания да и познания вообще. Поэтому его использование ниже носит интуитивный характер и будет основываться на субъективных представлениях читателя. За свою историю человечество создала немало весьма сложных проектов и систем в различных областях, к числу которых с полным основанием можно отнести и современные вычислительные системы (ВС) с разрабатываемым для них программным обеспечением (ПО). Поэтому обеспечение высокого качества разрабатываемых сложных программных проектов представляется не только чрезвычайно важной, но и весьма трудной задачей, носящей многоаспектный характер. В последнее время данному аспекту программной индуствии уделяется особое внимание.
Решение данной задачи можно обеспечивать двумя основными путями: (1) исчерпывающее тестирование готового программного средства (ПС), устранение всех ошибок и оптимизация его по заданным критериям; и (2) обеспечение высокого качества на всех этапах разработки ПС. Так как для большинства достаточно сложных ПС первый подход неприемлим, то наиболее реальным является второй, при котором вся задача разбивается на отдельные объекты (модули), имеющие хорошо обозримые структуру и функции, относительно небольшие размеры и сложность и структурно-функциональное объединение (композиция) которых позволяет решать исходную задачу. При таком модульном подходе сложность ПС редуцируется к существенно меньшей сложности составляющих его компонент, каждая из которых выполняет четкие функции, обеспечивающие в совокупности с другими компонентами требуемое функционирование ПС в целом. Метод программирования, когда вся программа разбивается на группы модулей, каждый со своей контролируемой структурой, четкими функциями и хорошо определенным интерфейсом с внешней средой, называется модульным программированием.
Поскольку модульный является единственной альтернативой монолитного (в виде единой программы) подхода, то вопрос состоит не в целесообразности разбивать или нет большую программу на модули, а в том - каков должен быть критерий такого разбиения. На сегодня практика программирования знает и использует целый ряд методов организации многомодульных ПС, когда разбиение на модули основывается на их объемных характеристиках в строках исходного текста, выделении однотипных операций и т.д. Однако наиболее развитым представляется критерий, в основе которого лежит хорошо известный принцип «черного ящика». Данный подход предполагает на стадии проектирования ПС представлять его в виде совокупности функционально связанных модулей, каждый из которых реализует одну из допустимых функций. При этом, способ взаимодействия модулей должен в максимально возможной степени скрывать принципы его функционирования и организации. Подобная модульная организация приводит к выделению модулей, которые характеризуются легко воспринимаемой структурой и могут проектироваться и разрабатываться различными проектировщиками и программистами. Более важный аспект состоит в том, что многие требуемые модификации сводятся к изменению алгоритмов функционирования отдельных модулей без изменения общей структурно-функциональной организации ПС в целом. Вопросы современной концепции модульного программирования базируются на ряде основных предпосылок, рассматриваемых, например, в книгах [1-3] и в цитируемой в них весьма обширной литературе.