Наконец, третий способ определения однострочных простых процедур базируется на использовании define-процедуры, имеющей для этого формат кодирования вида: define(F, F(x1::<Тип_1>, ..., xn::<Тип_n>) = <Выражение>(x1, ..., xn))
По этому формату define-процедура в качестве F-процедуры/функции от типированных (x1, ..., xn)-аргументов/переменных определяет выражение от этих же ведущих переменных. При этом, допускается использование не только типов, идентифицируемых typeфункцией, но и структурных типов. Фактические аргументы, определяемые их типированными формальными аргументами, могут принимать любые совместимые с указанными для них типами значения. Успешный вызов процедуры define возвращает NULL-значение. Вызов определенной таким образом процедуры/функции производится по конструкции F(x1,..., xn). Следующий фрагмент иллюстрирует применение define-процедуры для определения пользовательских процедур:
> define(G, G(x::integer, y::anything, z::fraction) = sqrt(x^3 + y^3 + z^3)); > interface(verboseproc = 3): eval(G); proc() local theArgs arg look me cf term, , , , , ; option `Copyright (c) 1999 Waterloo Maple Inc. All rights reserved.`; description "a Maple procedure automatically generated by define()"; me := eval procname, 1 ;( ) theArgs := args; look := tablelook '( procname'(theArgs), '[`/POS` 1,( G, 3), `/BIND` 1, 1,( `/x1` integer:: ), `/BIND` 2, 1,( `/x2` anything:: ), `/BIND` 3, 1,( `/x3` fraction:: ), `/PATTERN` (( `/x1`^3 + `/x2`^3 + `/x3`^3)^(1/2))]'); if look ≠ FAIL then eval(look, `/FUNCNAME` = procname) else 'procname'(theArgs) end if end proc > [G(42, 64., 19/99), G(42, 64., 1999), G(42., 64, 350/65), G(`REA`, `13.09.06`)]; 579.8551604 G(, 42 64. 1999, , ), G 42., 64, 7013, G(REA 13.09.06, ) > define(S, S(x, y::string, z::integer) = cat(x, "", y, "", z)); S(RANS, " - 13 сентября ", 2006); S(RANS, " - 13 сентября ", 2006) > proc(x, y::string, z::integer) cat(x, "", y, "", z) end (RANS, " - 13 сентября ", 2006); RANS - 13 сентября 2006 > S1:= (x, y, z) -> cat(x, "", y, "", z): S1(RANS, " - 13 сентября ", 2006); RANS - 13 сентября 2006 |
Второй пример фрагмента иллюстрирует вид исходного текста процедуры G, генерируемой пакетом по define-процедуре. В случае передачи определенной по define процедуре некорректных фактических аргументов (несоответствие числа фактических аргументов формальным и/или их типов) ее вызов возвращается невычисленным, как это иллюстрирует третий пример фрагмента. При этом, если определенная первым способом процедура допускает корректное выполнение в случае превышения числа фактических аргументов над формальными (игнорируя лишние), то в случае определенной третьим способом данная ситуация полагается ошибочной, возвращая вызов невычисленным. Напоминая второй способ определения процедур, третий отличается от него существенным моментом, позволяя использовать типизацию формальных аргументов. Между тем, третий способ определения процедур имеет определенные ограничения, не позволяя использовать в теле процедуры произвольные Maple-функции. Так, третий пример фрагмента иллюстрирует некорректность вызова определенной третьим способом S-процедуры (в ее теле использована cat-функция), тогда как определения эквивалентных ей процедур первым и вторым способом возвращают вполне корректные результаты. Следовательно, третий способ следует использовать весьма осмотрительно.
Подобным третьему способом определения пользовательской функции является применение специальной unapply-процедуры, имеющей следующий формат кодирования: unapply(<Выражение> {, <Ведущие переменные>})
где выражение определяет собственно тело самой функции, а ведущие переменные – последовательность ее формальных аргументов. В результате своего вызова unapply-процедура возвращает рассмотренную выше функциональную конструкцию в терминах (->)-оператора, как это иллюстрирует следующий весьма простой фрагмент:
> F:= unapply((gamma*x^2 + sqrt(x*y*z) + x*sin(y))/(Pi*y^2 + ln(x + z) + tan(y*z)), x, y, z); γ x2 + x y z + x sin( )y F := (x y z, , ) → 2 + ln(x + z) + tan(y z) π y > evalf(F(6.4, 5.9, 3.9)), F(m, n, p), evalf(F(Pi/2, Pi/4, Pi)); γ m2 + m n p + m sin(n)0.2946229694 , n2 + ln(m + p) + tan(n p), 1.674847463 π > W:=[unapply(sin(x),x), unapply(x,x), unapply(x^3,x)]; W(6.4); W := [sin, x -> x, x -> x3] [0.1165492049, 6.4, 262.144] |
Реализация unapply-процедуры базируется на использовании λ-исчисления, а сама процедура применяется, как правило, при использовании вычисляемых выражений для функциональных конструкций. Эта же процедура позволяет достаточно эффективно производить функциональные определения и в рамках структур (список, множество, массив). В наиболее же массовых случаях определения пользовательских функций оба представленных средства функциональный (->)-оператор и unapply-процедуру можно полагать эквивалентными, хотя в общем случае и имеются существенные различия.
При необходимости определения функции с неопределенным числом формальных аргументов в общем случае второй аргумент unapply-процедуры (последовательность ведущих переменных) может не кодироваться, как это иллюстрирует следующий фрагмент:
> SV:= unapply(evalf([nargs, sqrt(sum(args['k']^2, 'k' = 1..nargs))/nargs], 6)); SV ( ) → nargs, nargsk∑nargs = 1 argsk2 := > [6*SV(64,39,59,44,10,17), evalf(SV(64,39,59,44,10,17), 6)]; ⇒ [[36, 11423 ], [6. 17.8130, ]]> VS:= () -> evalf([nargs, sqrt(sum(args['k']^2, 'k' = 1..nargs))/nargs], 6); nargs, ' 'nargsk∑nargs = 1 args' 'k 2 , 6VS := ( ) → evalf > k:= 'k': [VS(64,39,59,44,10,17), evalf(VS(64,39,59,44,10,17), 6)]; ⇒ [6., 17.8130], [6., 17.8130]] |
Первый пример фрагмента представляет SV-функцию от неопределенного числа формальных аргументов, определенную на основе unapply-процедуры, а второй - VS-функцию, определенную на основе функционального (->)-оператора и эквивалентную (по реализованному алгоритму) SV-функции. Вместе с тем, как иллюстрируют примеры фрагмента, между обоими определениями имеется существенное различие, а именно: игнорируется ряд встроенных функций (evalf, convert) при определении SV-функции. Поэтому, в общем случае способ определения функции на основе функционального (->)-оператора является более универсальным.
Следует отметить, что представленный способ определения пользовательской функции на основе define-процедуры по целому ряду характеристик предпочтительнее способа, базирующегося на функциональном (->)-операторе или unapply-процедуре, как это иллюстрирует следующий весьма поучительный фрагмент:
> R:=[42,47,67,62,89,96]: S:=[64,59,39,44,17,10]: define(V,V(x::integer)=interp(R,S,x)); V(95); 11 > map(GS, [42, 47, 67, 62, 89, 96]); ⇒ [64, 59, 39, 44, 17, 10] > define(H, H(x::anything, y::integer) = sqrt(x^2 + y^2)); > H(sqrt(75), 5), evala(H(sqrt(75), 5)), H(sqrt(75), 59.47); ⇒ 100 10 H(, , 5 3 59.47, )> x:= [a, b, c, d, e, f]: define(DD, DD(x[k]::prime$'k'=1..6) = (sqrt(sum(x[k]^2, 'k'=1..6)))); > evala(DD(3, 1999, 71, 13, 17, 7)), evala(DD(64, 59, 39, 44, 17, 10)); 4001558 DD(, 64 59 39 44 17 10, , , , , )> restart; define(R, R(x::even, y::odd) = sqrt(x^2 + y^2)), R(10, 17)^2; ⇒ 389 > define(R, R(x::even, y::odd) = sqrt(x^3 + y^3)), R(10, 17); Error, (in DefineTools:-define) R is assigned > R:= 'R': define(R, R(x::even, y::odd) = sqrt(x^3 + y^3)), R(10, 17)^2; ⇒ 5913 > define(AG, AG(0)=1, AG(1)=2, AG(2)=3, AG(t::integer) = AG(t-3) + 2*AG(t-2) + AG(t-1)); > map(AG, [10, 17, 15, 18, 20]); ⇒ [1596, 336608, 72962, 723000, 3335539] > define(G3, G3(seq(x||k::integer, k=1 .. 6)) = sum(args[k], k=1 .. nargs)/nargs); > 3*G3(10, 17, 39, 44, 95, 99); ⇒ 152 > define(G4, G4()=sum(args[k], 'k'=1..nargs)/nargs): 3*G4(10, 17, 39, 44, 95, 99); ⇒ 152 > define(G6, G6() = [nargs, args]): Error, (in insertpattern) Wrong kind of arguments > G7:= () -> [nargs, args]: G7(10, 17, 39, 44, 95, 99); ⇒ [6, 10, 17, 39, 44, 95, 99] > define(G8, G8() = args), define(G9, G9() = nargs), G8(10, 17), G9(10, 17, 39, 44); ⇒ 10, 4 > define(S1, define(S2, S2()= sum(args[k], 'k'=1..nargs)), S1() = S2(V, G, S)*nargs); > S1(10, 17, 39, 44, 95, 99), S2(10, 17, 39, 44, 95, 99); ⇒ 6 V + 6 G + 6 S, 304 |
Фрагмент иллюстрирует реакцию пакета на переопределения функции, определенной на основе define-подхода, и вызовы пользовательской H-функции от двух типированных аргументов. На примере H-функции проиллюстрированы следующие два весьма принципиальных момент, а именно:
(1) результат вызова define-определенной функции в общем случае требует последующей обработки evala-функцией для получения окончательного результата;
(2) вызов define-определенной функции на фактических аргументах, не отвечающих оп- ределенным для них типам, возвращается невычисленным.