Смекни!
smekni.com

Программирование и разработка приложений в Maple (стр. 43 из 135)

> x:= 64: proc(y) global x; x:= 0; y^(x+y) end proc(10); evalf(2006/x); ⇒ 10000000000 Error, numeric exception: division by zero

> x:= 64: proc(y) global x; x:= 0; y^(x+y) end proc: evalf(2006/x); ⇒ 31.34375000

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

Локальные переменные также можно типировать подобно аргументам, но действие этого типирования имеет смысл лишь при установке kernelopts(assertlevel=2), например:

> P:= proc(a, b) local c::integer; c:=a+b end proc: P(42.47,6); ⇒ 48.47

> kernelopts(assertlevel=2): P(42.47,6);

Error, (in P) assertion failed in assignment, expected integer, got 48.47

Если для переменных, используемых в определении процедуры, не определена область их действия (local, global), то Maple-язык классифицирует их следующим образом. Каждая переменная, получающая в теле процедуры определение по (:=)-оператору либо переменная цикла, определяемая функциями {seq, add, mul} полагается локальной (local), остальные полагаются глобальными (global) переменными. При этом, если переменные for-цикла не определены локальными явно, то выводится предупреждающее сообщение вида "Warning, `k` is implicitly declared local to procedure `P`”, где k и P – переменная цикла и имя процедуры соответственно. Тогда как уже для функций sum и product переменные цикла рассматриваются глобальными, не выводя каких-либо сообщений, что предполагает их явное определение в local-секции. Однако вне зависимости от наличия предупреждающих сообщений рекомендуется явно указывать локальные и глобальные переменные, что позволит не только избегать ошибок выполнения, но и более четко воспринимать исходный текст процедуры. Следующий фрагмент иллюстрирует вышесказанное:

> G:=2: A:=proc(n) V:=64: [args, assign('G', 5), assign('V', 9), assign(cat(H, n), `h`)] end proc: Warning, `V` is implicitly declared local to procedure `A`

> [A(99), G, V, A(10), whattype(H9), H9]; ⇒ [[99], 5, 9, [10], symbol, H9]

> k:= 64: H:= proc() product(args[k], k=1 .. nargs)/sum(args[k], k=1 .. nargs) end proc: > [k, H(42, 47, 62, 67, 96, 89, 10, 17, 4), k]; Error, (in H) invalid subscript selector

> k:=64: P:= () -> [seq(args[k], k=1..nargs)]: P(1, 2, 3), k; ⇒ [1, 2, 3], 64

> k:=64: P:= () -> [sum(args[k], k=1..nargs)]: P(1, 2, 3), k; Error, (in P) invalid subscript selector

> k:=64: P:= () -> [product(args[k], k=1..nargs)]: P(1, 2, 3), k; Error, (in P) invalid subscript selector

> k:=64: P:=proc() for k to nargs do end do end proc: P(1, 2, 3), k; ⇒ 64

Warning, `k` is implicitly declared local to procedure `P`

Таким образом, в указанных случаях соответствующие переменные процедуры при ее вычислении неявно декларируются локальными с выводом или без предупреждающих сообщений. С другой стороны, глобальные переменные даже без их явного декларирования в global-секции можно генерировать в рамках процедуры, как это иллюстрирует 1й пример предыдущего фрагмента. Делать это позволяет процедура assign. Однако работа с такими глобальными переменными чревата непредсказуемыми последствиями. Таким образом, практика программирования в среде Maple-языка рекомендует следовать следующим двум правилам определения области действия переменных:

(1) глобальными определять переменные, лишь используемые в режиме ”чтения”;

(2) локальные переменные определять явно в local-секции процедуры.

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

> restart; P:= proc(a, b) local x, y; x, y:= 42, 64; x, y end proc:

> x, y:= 42, 64: P(a, b), x, y; ⇒ 42, 64, 42, 64

> restart; P:= proc(a, b) local x, y; x, y:= a, b; x, y end proc:

> x, y:= 42, 64: P(a, b), x, y; ⇒ a, b, 42, 64

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

По assign-процедуре в теле процедур можно назначать выражения как локальным (заданным явно), так и глобальным (заданным явно либо неявно) переменным. Однако здесь имеется одно весьма существенное отличие. Как известно, пакет не допускает динамического генерирования имен в левой части (:=)-оператора присваивания, тогда как на основе assign-процедуры это возможно делать. Это действительно существенная возможность, весьма актуальная в целом ряде задач практического программирования [103]. Между тем, если мы по процедуре assign в теле процедуры будем присваивать выражения локальным переменным и сгенерированным одноименным с ними переменным, то во втором случае присвоения производятся именно глобальным переменным, не затрагивая локальных. Нижеследующий пример весьма наглядно иллюстрирует вышесказанное.

> restart; V42, G47:= 10, 17: proc() local V42, G47; assign(V42=64, G47=59); assign(cat(V, 42)=100, cat(G, 47)=200); [V42, G47] end proc(), [V42, G47]; ⇒ [64, 59], [100, 200]

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

Следует еще раз отметить, что для предложений присвоения в процедурах в целом ряде случаев использование assign-процедуры является единственно возможным подходом. Однако, при таком подходе в общем случае требуется, чтобы левая часть уравнения x=a в assign(x=a) была неопределенным именем, т.е. для нее должно выполняться соотношение type(x, 'symbol') = true. И здесь вполне допустимо использование конструкций следующего общего формата кодирования: assign(op([unassign('<Имя>'), <Имя>]) = <Выражение>)

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

> x:= 64: assign(op([unassign(x), x]) = 59); x; ⇒ 64

Error, (in unassign) cannot unassign '64' (argument must be assignable)

> P:= proc() end proc: M:= module() end module: T:= table([]): A:= array([]):

> map(whattype, map(eval, [P, M, T, A])); ⇒ [procedure, module, table, array]

> seq(assign(op([unassign(k), k])=59),k=[P,M,T,A]); [P,M,T,A], map(type, [P,M,T,A],'odd'); [59, 59, 59, 59], [true, true, true, true]

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

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

Процедура

global (x = a)

local (x = b)

(1) global (x = a) ⇒⇒⇒⇒⇒⇒⇒⇒ ⇒⇒ global (x = a)

(2) global (x = a) ⇒⇒⇒⇒⇒⇒⇒⇒ ⇒⇒ global (x = a)

local (x = b)

(3) x ⇒⇒⇒⇒⇒⇒⇒⇒⇒ true ⇒⇒ ⇒⇒ x::{name|symbol} type(x, {'name|symbol'})

Error-ситуация

(4) (x = a) ⇒⇒⇒⇒⇒⇒ false ⇒⇒ ⇒⇒ x = a

В случае (1) явно либо неявно определенная х-переменная процедуры на всем протяжении текущего сеанса сохраняет свое значение до его переопределения вне или в самой процедуре. В случае (2) определенная локально в теле процедуры х-переменная в рамках процедуры может принимать значения, отличные от ее глобальных значений вне ее, т.е. в процедуре временно подавляется действие одноименной с ней глобальной х-переменной. Однако здесь имеют место и особые случаи (3, 4), не охватываемые стандартным механизмом. Для ряда функций, использующих ранжированные переменные (например, product, sum), возможны две ситуации, если такие переменные не декларировались в процедуре явно. Прежде всего, как отмечалось выше, не выводится предупреждающих сообщений о том, что они предполагаются локальными. Следовательно, они согласно трактовке языка Maple должны рассматриваться глобальными. Между тем, если на момент вызова процедуры, содержащей такие функции, ранжированная х-переменная была неопределенной (случай 3), то получая значения в процессе выполнения процедуры, после выхода из нее она вновь становится неопределенной, т.е. имеет место глобальное поведение переменной. Если на момент вызова процедуры х-переменная имела значение, то выполнение процедуры инициирует ошибочную ситуацию, а значение х-переменной остается неизменным (случай 4). Рассмотренные ситуации еще раз говорят в пользу явного определения входящих в процедуру переменных.