Таблица 8
Proc:= proc() ... end proc | assign(Proc, proc() ... end proc) | |
global | global | global |
local | local | local |
не декларирована | local | global |
Представленный в табл. 8 принцип декларирования идентификаторов подпроцедур в полной мере распространяется и на идентификаторы вообще, что весьма наглядно иллюстрирует следующий достаточно простой фрагмент:
> restart: H:= proc(x) local h; h:= x^2 end proc: [H(64), h]; ⇒ [4096, h] > restart: H:= proc(x) global h; h:= x^2 end proc: [H(64), h]; ⇒ [4096, 4096] > restart: H:= proc(x) h:= x^2 end proc: [H(64), h]; ⇒ [4096, h] Warning, `h` is implicitly declared local to procedure `H` > restart: H:= proc(x) local h; assign(h = x^2); h end proc: [H(64), h]; ⇒ [4096, h] > restart: H:= proc(x) global h; assign(h = x^2); h end proc: [H(64), h]; ⇒ [4096, 4096] > restart: H:= proc(x) assign(h = x^2); h end proc: [H(64), h]; ⇒ [4096, 4096] |
Данное обстоятельство следует учитывать при практическом программировании, иначе в целом ряде случаев его игнорирование может быть причиной весьма серьезных ошибок, как отслеживаемых ядром пакета, так и семантических.
Таким образом, Maple-язык позволяет создавать вложенные процедуры, для которых поддерживается не только возможность определения одной процедуры в теле другой, но и возврат процедурой другой процедуры в качестве ее выхода. Поддерживаемый Mapleязыком механизм вложенных процедур (lexical scoping) обеспечивает доступ вложенных процедур к переменным, находящимся в окружающих их процедурах. Этот аспект лежит в основе обеспечения механизма инкапсуляции, на котором базируется современное объектно-ориентированное программирование. При этом, для глобальных переменных поддерживается только режим их разделения вложенными процедурами.
Процедуры допускают любой уровень вложенности, определяемый только объемом памяти, доступной рабочей области пакета. Однако, доступность внутренних процедур в текущем сеансе определяется двумя моментами: (1) типом внутренней процедуры (local, global) и (2) ее режимом использования. Если внутренняя процедура определена локальной (local), то прямой доступ к ней невозможен извне содержащей ее главной процедуры, тогда как при определении ее глобальной (global) ситуация несколько иная, а именно. Сразу же после вычисления определения главной процедуры все ее внутренние процедуры, включая и глобальные, остаются неопределенными, т.е. недоступными извне содержащей ее процедуры. И только после первого вызова главной процедуры все ее внутренние процедуры, определенные тем либо иным способом глобальными, становятся доступными в текущем сеансе вне содержащей их процедуры. Следующий фрагмент весьма наглядно иллюстрирует вышесказанное.
> P:= proc() local P1; P1:= () -> `+`(args); P1(args) end proc: > map(type, [P, P1], 'procedure'); ⇒ [true, false] > P(64, 59, 39, 10, 17): map(type, [P, P1], 'procedure'); ⇒ [true, false] > restart; P:=proc() global P1; P1:= () -> `+`(args); P1(args) end proc: > map(type, [P, P1], 'procedure'); ⇒ [true, false] > P(64, 59, 39, 10, 17): map(type, [P, P1], 'procedure'); ⇒ [true, true] > restart; P:=proc() assign('P1' = (() -> `+`(args))); P1(args) end proc: > map(type, [P, P1], 'procedure'); ⇒ [true, false] > P(64, 59, 39, 10, 17): map(type, [P, P1], 'procedure'); ⇒ [true, true] > restart; P:=proc() global P1, P2; P1:= () -> `+`(args); P2:= () -> `*`(args); if nargs =3 then P1(args) else P2(args) end if end proc: map(type, [P, P1, P2], 'procedure'); [true, false, false] > P(64, 59, 39, 10, 17): map(type, [P, P1, P2], 'procedure'); ⇒ [true, true, true] > restart; P:= proc() local P1; P1:= proc() global P2; P2:= () -> `+`(args); [args] end proc; `+`(args) end proc: map(type, [P, P1, P2], 'procedure'); ⇒ [true, false, false] > P(64, 59, 39,10, 17): map(type, [P, P1, P2], 'procedure'); ⇒ [true, false, false] > restart; P:= proc() local P1; P1:= proc() global P2; P2:= () -> `+`(args); [args] end proc; P1(args) end proc: map(type, [P, P1, P2], 'procedure'); ⇒ [true, false, false] > P(64, 59, 39,10, 17): map(type, [P, P1, P2], 'procedure'); ⇒ [true, false, true] |
Таким образом, вложенные (глобальные относительно содержащей ее главной процедуры) процедуры становятся доступными в текущем сеансе только после первого вызова главной процедуры. При этом, как иллюстрируют последние примеры фрагмента, в случае более одного уровня вложенности подпроцедуры, определенные глобальными, становятся доступными в текущем сеансе только после реального вызова содержащих их подпроцедур.
При этом, кажущаяся «вложенность» следующего типа
P:= proc() global P; P:= proc() end proc; [args] end proc:
в качестве вложенности рассматриваться не может и представляет собой исключительный случай, как это наглядно иллюстрирует следующий простой фрагмент:
> restart; P:=proc() global P; P:=() -> `+`(args); P(args); [args] end proc: eval(P); proc() global P; P := () -> `+`(args); P(args); [args] end proc > P(64, 59, 39, 10, 17), eval(P), P(64, 59, 39, 10, 17); ⇒ [64, 59, 39, 10, 17], () -> `+`(args), 189 |
Следовательно, во избежание недоразумений не рекомендуется использовать в качестве имен глобальных переменных имена процедур, в которых они определяются.
В свете вышесказанного довольно полезной представляется процедура intproc(P), обеспечивающая проверку процедуры Р быть главной или вложенной/внутренней. Успешный вызов процедуры возвращает последовательность из двух списков, первый из которых определяет процедуры текущего сеанса, идентичные исходной процедуре Р, тогда как второй список определяет процедуры, содержащие процедуру Р (или идентичную ей) в качестве внутренних/вложенных процедур. Первый элемент обоих списков – analogous и innet – определяет тип содержащихся в них элементов – аналогичная (главная либо внутренняя/вложенная, но глобальная) и внутренняя/вложенная соответственно. Нижеследующий фрагмент представляет исходный текст процедуры и примеры ее применения.
intproc := proc(P::symbol) local a b k x y, , , , ; if type(eval(P), 'symbol') then return FAIL elif type(P, 'procedure') then try P( ) catch : NULL end try ; assign(a = {anames('procedure')}, x = ['analogous'], y = ['innet']); b := {seq(`if`("" || a k[ ][1 .. 3] = "CM:", NULL, a k[ ]), k = 1 .. nops(a))} else error "<%1> has `%2`-type but should be procedure", P, whattype eval(( P)) end if; if member(P b, ) then for k in b minus {P} do if Search1(convert(eval( )k , 'string'), convert(eval(P), 'string'), ' 't ) then if t = ['coincidence'] then x := [op( )x , k] else y := [op( )y , k] end if else next end if end do; x y, else FAIL end if end proc > P:= proc() [args] end proc: P1:= proc() [args]; end proc: T:= table([]): > P2:= proc() local P1; P1:= proc() [args] end proc;; P1(args) end proc: > P3:= proc() global P1; P1:= proc() [args] end proc;; P1(args) end proc: intproc(P); [analogous, P1], [innet, P3, P2] > intproc(P1); ⇒ [analogous, P], [innet, P3, P2] > intproc(P2); ⇒ [analogous], [innet] > intproc(P3); ⇒ [analogous], [innet] > intproc(AGN); ⇒ FAIL > intproc(T); Error, (in intproc) <T> has `table`-type but should be procedure |
Если в качестве фактического аргумента Р выступает символьное выражение, то вызов процедуры возвращает FAIL-значение по причине невозможности установить истинное определение символа (возможно, процедура из библиотеки, логически не сцепленной с главной Maple-библиотекой). В случае типа аргумента Р, отличного от `procedure`, возникает ошибочная ситуация. Процедура intproc имеет ряд достаточно интересных приложений.
Дальнейшее рассмотрение целесообразно начать с CompSeq-функции, позволяющей в определенной степени автоматизировать процесс создания процедур на основе последовательности Maple-предложений. Для организации вычислительных последовательностей (ВП) в виде невычисляемых конструкций (своего рода макетов вычислительных блоков) Maple-язык располагает специальной CompSeq-функцией, имеющей следующий формат кодирования:
CompSeq(locals = L1, globals = L2, params = L3, <Список ВК>)
где в качестве первых трех необязательных аргументов выступают списки соответственно локальных (L1), глобальных (L2) переменных ВП и параметров (L3). Если локальные переменные областью своего определения имеют только тело ВП, то глобальные - текущий сеанс, а параметры могут передаваться в ВП извне ее, как и воспринимаемые извне значения ее глобальных переменных. Последний обязательный аргумент CompSeq-функции представляет собой список вычисляемых конструкций (ВК) вида <Id-переменной> = <Выражение>; последняя ВК последовательности и возвращает окончательный результат ее вычисления. При этом, ВП может быть упрощена, оптимизирована, а также конвертирована в форму процедуры, и наоборот. Следующий фрагмент иллюстрирует использование CompSeq-функции для создания ВП, затем конвертацию полученной конструкции в Maple-процедуру с ее последующим ее выполнением:
> GS:= CompSeq(locals=[x, y, z], globals=[X, Y, Z], params=[a, b, c], [x=a*sqrt(X^2 +Y^2 + Z^2), y = (x+b)*exp(Z)/(ln(X) + sin(Y)), z = x*y/(a*x + b*y), x*y/(x + z)]); GS := CompSeqlocals = [x y z, , ], globals = [X Y Z, , ], params = [a b c, , ], x = a X2 + Y2 + Z2 , y = ln((Xx + ) + bsin) e(ZY), z = a xx y + b y, xx y + z > SG:= convert(GS, 'procedure'); SG := proc(a, b, c) local x y z, , ; global X Y Z, , ; x := a×(X^2 + Y^2 + Z^2)^(1/2 ;) y := (x + b)×exp(Z)/(ln(X) + sin(Y)); z := x×y/(a×x + b×y); x y× /(x + z) end proc > X:= 6.4: Y:= 5.9: Z:= 3.9: evalf(SG(42, 47, 67), 12); ⇒ 14613.2142097 |
Возможности CompSeq-функции позволяют описывать относительно несложный вычислительный алгоритм в виде последовательностей простых ВК, в последующем конвертируемых в процедуры, определяющие законченные вычислительные блоки, вызовы которых можно производить на заданных списках фактических значений их формальных аргументов. Это позволяет существенно упрощать решение многих прикладных, но достаточно простых задач.