Технология модульного программирования охватывает макроуровень разработки ПО и позволяет решать важные задачи программной индустрии. Одним из основных подходов, обеспечивающих модульность программ, является механизм процедур, относительно Maple-языка рассматриваемый нами в настоящей главе книги.
Выделение в большой задаче локальных подзадач с достаточно большой частотой использования и применимости позволяет не только существенно продвинуть вопрос повышения ее модульности, но и повысить эффективность и прозрачность разрабатываемых программных средств. Наряду с этим, модульный подход позволяет сделать доступными отдельно оформленные виды и типы часто используемых работ для многих приложений. Достаточно развитый механизм процедур Maple-языка во многих отношениях отвечает данному решению.
Процедура в среде Maple-языка имеет следующую принципиальную структуру: proc(<Последовательность формальных аргументов>){::Тип;} local <Последовательность идентификаторов>; Описательная global <Последовательность идентификаторов>; часть options <Последовательность параметров>; определения uses <Последовательность имен пакетных модулей>;description <Описание>; процедуры
<Т Е Л О процедуры>
end proc {;|:}
Заголовок процедуры содержит ключевое proc-слово со скобками, в которых кодируется последовательность формальных аргументов процедуры; данная последовательность может быть и пустой, т.е. минимально допустимый заголовок процедуры имеет вид proc( ). Позади заголовка может кодироваться тип, относящийся к типу возвращаемого процедурой результата. Подробнее об этом говорится ниже. Ключевые слова local, global и options определяют необязательные описательные секции процедуры, представляющие соответственно последовательности идентификаторов локальных, глобальных переменных и параметров (опций) процедуры. Необязательная description-секция содержит последовательность строк, описывающих процедуру. Если данная секция представлена в определении процедуры, то она будет присутствовать и при выводе последней на печать. Практически все библиотечные процедуры пакета содержат description-секцию, хотя она и не выводится на печать. При этом, если данная секция содержит несколько предложений, то она должна кодироваться последней, иначе инициируется ошибочная ситуация, как это иллюстрирует нижеследующий простой фрагмент:
> P:= proc() description "Сумма аргументов"; `+`(args); `+`(args) end proc: print(P); proc() description "Сумма аргументов"; `+`(args); `+`(args) end proc
> P(42, 47, 67), op(5, eval(P)); ⇒ 156, "Сумма аргументов"
> P:= proc() description "Сумма аргументов"; `+`(args); `+`(args); option trace; end proc; Error, reserved word `option` or `options` unexpected
В Maple 10 дополнительно к указанным определение процедуры допускает использование необязательной uses-секции, определяющей имена пакетных модулей, используемых процедурой. Например, кодирование секции «uses LinearAlgebra» эквивалентно использованию в процедуре use-предложения следующего формата: use LinearAlgebra in < ... > end use;
Данная секция не идентифицируется при выводе либо возврате определения процедуры, исчезая подобно use-предложению, как это иллюстрирует следующий пример: > P:= proc() local a,b,c; global d; uses LinearAlgebra; option remember; description "grsu"; BODY end proc;
P := proc() local a, b, c; global d; option remember; description "grsu"; BODY end proc И не возвращается ее содержимое по вызовам op(k, eval(P)) (k=1..8) подобно случая других секций описания произвольной процедуры Р.
На наш взгляд, секция uses носит непринципиальный характер, в определенной мере лишь упрощая (сокращая) оформление процедуры, тогда как с точки зрения читабельности исходного текста использование в теле use-предложений даже более предпочтительно. Не говоря уже о том, что данное непринципиальное новшество ведет к несовместимости процедур «сверху-вниз» относительно релизов пакета.
Секции local, global, options, uses и description составляют описательную часть определения Maple-процедуры, которая в общем случае может и отсутствовать. Управлять выводом тела процедуры можно посредством установки переменной verboseproc interfaceпроцедуры Maple-языка. Минимальной конструкцией, распознаваемой ядром в качестве процедуры, является структура следующего весьма простого вида, а именно: Proc := proc() end proc: whattype(eval(Proc)); ⇒ procedure
Распознаваемый пакетом Proc-объект в качестве процедуры, между тем, особого смысла не имеет, ибо вызов процедуры Proc(args) на любом кортеже фактических аргументов всегда возвращает NULL-значение, т.е. ничего. Для возможности вызова процедуры ее определение присваивается некоторому Id-идентификатору Id := proc({ |Args}) ... end proc {:|;}, позволяя после его вычисления производить вызов процедуры по Id({|Args})конструкции с заменой ее формальных Args-аргументов на фактические Args-аргументы. Наряду с классическим определением именованной процедуры Maple-язык допускает использование и непоименованных процедур, определения которых не присваиваются какому-либо идентификатору. Для вызова непоименованных процедур служит конструкция следующего вида:
proc({ |Args}) ... end proc(Args)
возвращающая результат вызова процедуры на заданных в круглых скобках фактических аргументах, как это иллюстрирует следующий простой фрагмент:
> proc() [nargs, args] end proc(42, 47, 67, 89, 96, -2, 95, 99); ⇒ [8, 42, 47, 67, 89, 96, -2, 95, 99]
> proc(n) sum(args[k], k=2..nargs)^n end proc(3, x, y, z, a, b, c, h); ⇒ (x+y+z+a+b+c+h)3
> D(proc(y) (y^9 + 3*y^5 - 99)/6*(3*y^2 - 256) end proc)(9); ⇒ 2648753541
> a*proc(x) x^2 end proc(42) + b*proc(y) y^2 end proc(47); ⇒ 1764 a + 2209 b
> D(proc(y) (y^9 + 2*y^5 - 99)/13*(3*y^2 - 256) end proc);
proc(y) 1/13*(9*y^8+10*y^4)*(3*y^2-256)+6/13*(y^9+2*y^5-99)*y end proc
> restart: (D@@9)(proc(x) G(x) end proc); ⇒ proc(x) (D@@9)(G)(x) end proc
Непоименованные процедуры можно использовать в ряде выражений и обрабатывать некоторыми функциями и операторами, как это иллюстрируют примеры фрагмента. В этом смысле наиболее часто непоименованные процедуры используются в конъюнкции с функциями {map,map2}, а также с другими функциями и операторами, допускающими неалгебраические выражения в качестве своих фактических аргументов и операндов. В то же время, как правило, рекомендуется использовать именно именованные Maple-процедуры, что позволяет исключать различного рода ошибки.
Тело процедуры содержит текст описания алгоритма решаемой ею задачи с использованием рассмотренных выше средств Maple-языка, а именно: данных и их структур допустимых типов, переменных, функций пакетных, модульных и пользовательских, других процедур и т. д., логически связанных управляющими структурами следования, ветвления и цикла, рассмотренными выше. Данное описание должно удовлетворять правилам допускаемого языком синтаксиса. Завершается процедура кодированием закрывающей процедурной скобки `end proc`. Представленная структура является определением процедуры, которое активизируется только после его вычисления. При этом, во время вычисления процедуры не производится выполнения предложений тела процедуры, но интерпретатор Maple-языка производит все возможные упрощения тела процедуры, являющиеся простым типом ее оптимизации. Реальное же выполнение тела процедуры производится только в момент ее вызова с передачей ей фактических аргументов, значения которых замещают все вхождения в тело формальных аргументов. Завершается определение процедуры обязательным предложением `end proc {:|;}` (закрывающей скобкой).
Вторым способом определения процедур является использование функционального оператора (->), позволяющего представлять процедуры в нотации классической операции отображния, а именно в следующем простом виде:
(Args) -> <Выражение от Args-переменных>
При этом, в случае единственного аргумента кодирование его в скобках не обязательно. Присвоение данной конструкции идентификатору позволяет определить процедуру, например: > G:= (x, y) -> evalf(sqrt(x^2 + y^2)): G(42, 47); ⇒ 63.03173804. Последовательность формальных аргументов в таким образом определяемой процедуре может быть пустой, а ее тело должно быть единым выражением либо if-предложением языка:
> W:= () -> `if`(member(float, map(whattype, {args})), print([nargs, [args]]), NULL): > W(42, 47, 19.98, 67, 96, 89, 10,3, `TRG`); ⇒ [9, [42, 47, 19.98, 67, 96, 89, 10, 3, TRG]] > H:= x -> if (x < 42) then 10 elif (x >= 42)and(x <= 99) then 17 else infinity end if: > map(H, [39, 49, 62, 67, 95, 98, 99, 39, 17, 10, 39]); ⇒ [10, 17, 17, 17, 17, 17, 17, 10, 10, 10, 10] > [D(x -> sin(x))(x), (x -> sqrt(x^3 + 600))(10)]; ⇒ [cos(x), 40] > map((n) -> `if`(type(n/4, 'integer'), n, NULL), [seq(k, k= 42..64)]) ⇒ [44, 48, 52, 56, 60, 64] > Z:= () -> if sum(args[k], k= 1 .. nargs) > 9 then args else false end if: > [Z(3, 6, 8, 34, 12, 99), Z(3, -6, 8, -21, 6, 10)]; ⇒ [3, 6, 8, 34, 12, 99, false] |
При этом, следует иметь в виду, что второй способ определения предназначен, прежде всего, для простых однострочных процедур и функций, ибо не поддерживает механизма локальных и глобальных переменных, а также опций. Между тем, в целом ряде случаев он оказывается весьма полезным приемом программирования. Подобно первому способу определения процедур, второй также допускает использование непоименованных процедур, используемых в тех же случаях, что и первый способ. Как это иллюстрируют последние примеры предыдущего фрагмента (см. прилож. 3 [12]).