Երկու սերտ հասկացություններ՝
բարձր կարգի ֆունկցիա և
անանուն ֆունկցիա, որոնք լայնորեն կիրառվում են ֆունկցիոնալ ծրագրավորման մեջ, որոնց տրամադրած մեխանիզմները հնարավորություն են տալիս կազմակերպել ավելի բնական, ավելի գեղեցիկ, ավելի ընթեռնելի ծրագրեր։
Ֆունկցիան կոչվում է
բարձր կարգի, եթե նրա արգումենտներից գոնե մեկը ֆունկցիա է, կամ նրա վերադարձրած արժեքն է ֆունկցիա։
Ես ուզում եմ բարձր կարգի ֆունկցիաները ցուցադրել տրված ֆունկցիայի որոշյալ ինտեգրալի թվային հաշվման օրինակով։ Ենթադրենք պետք է իրականացնել
integral
ֆունկցիան, որն արգումենտում ստանում է
f(x) ինտեգրվող ֆունկցիան և
[a;b] ինտեգրման միջակայքը։
integral(f,a,b)=b∫af(x)dx
Այս
integral
ֆունկցիան
երկրորդ կարգի է, որովհետև նրա
f
արգումենտը
առաջին կարգի ֆունկցիա է։
Ինտեգրալի թվային հաշվման համար ընտրենք
սեղանների մեթոդը, որի դեպքում ինտեգրման միջակայում
f ֆունկցիայի գրաֆիկը մոտարկվում է ուղիղ գծով, իսկ ինտեգրալի արժեքը ընդունվում է
(a,0),
(a,f(a)),
(b,f(b)) և
(0,b) գագաթներով սեղանի մակերեսին հավասար։
trapezoid(f,a,b)=(b−a)f(a)+f(b)2
Common Lisp լեզվով այս բանաձևը ծրագրավորվում է հետևյալ կերպ.
(defun trapezoid (f a b)
(* (- b a) (/ (+ (funcall f a) (funcall f b)) 2.0)))
integral
ֆունկցիայի արգումենտում տրված
f
ֆունկցիա-արգումենտը ֆունկցիայի մարմնում օգտագործված է
funcall
ֆունկցիայի միջոցով, որը տրված ֆունկկցիա-օբյեկտը կիրառում է տրված արգումենտների նկատմամբ։
C++11 լեզվով նույն
integral
ֆունկցիան կարելի է գրել հետևյալ կերպ.
double trapezoid( std::function<double(double)> f, double a, double b )
{
return (b - a) * ((f(a) + f(b)) / 2);
}
Եթե այս ֆունկցիայով հաշվենք, օրինակ
f(x)=x2 ֆունկցիայի ինտեգրալը
[−1;1] միջակայքում, ապա կստանանք
2 արժեքը՝ իրական
2/3-ի փոխարեն։
Պարզ է, որ սեղանների մեթոդը կարող է բավարար ճշտություն ապահովել միայն այն դեպքում, երբ ինտեգրման միջակայքը շատ փոքր է։ Օգտագործելով այդ փաստը, սահմանեմ
integral
ֆունկցիան, որի
ε արգումենտը ցույց է տալիս ինտեգրման միջակայքի ամենամեծ թույլատրելի չափը։ Այն դեպքում, երբ
b−a>ε, միջակայքը կկիսեմ երկու հավասար մասերի և իրար կգումարեմ այդ երկու մասերում հաշված ինտեգրալների արժեքները։ Այլ կերպ ասած, ինտեգրալի հաշվման նոր բանաձևն է հետևյալը.
integral(f,a,b,ε)={trapezoid(f,a,b),|b−a|≤ε,integral(f,a,a+b2,ε)+integral(f,a+b2,b,ε),|b−a|>ε.
Այս
integral
ֆունկցիան նույնպես երկրորդ կարգի է, քանի որ նրա արգումենտներում ամենաբարձր կարգը առաջինն է։
integral
ֆունկցիայի սահմանումը Common Lisp լեզվով ունի այսպիսի տեսք.
(defun integral (f a b &optional (eps 0.001))
(if (<= (- b a) eps)
(trapezoid f a b)
(let ((m (/ (+ a b) 2)))
(+ (integral f a m eps) (integral f m b eps)))))
Նույն ֆունկցիան C++11 լեզվով կունենա հետևյալ տեսքը։
using mathfunc = std::function<double(double)>;
double integral( mathfunc f, double a, double b, double eps = 0.001 )
{
if( b - a <= eps)
return (b - a) * ((f(a) + f(b)) / 2);
auto m((a + b) / 2);
return integral(f, a, m, eps) + integral(f, m, b, eps);
}
Հիմա, ենթադրենք, ուզում եմ հաշվել
f(x)=x3 ֆունկցիայի ինտեգրալը
[0;2] միջակայքում: Նախ սահմանեմ այդ ֆունկցիան Lisp լեզվով.
(defun f (x) (* x x x))
Ապա
integral
ֆունկցիայի օգնությամբ հաշվեմ սրա ինտեգրալը տրված միջակայքում.
(integral #'qub 0 2.0) ; => 4.0
Եթե պետք լինի հաշվել, օրինակ,
f(x)=x2−3x ֆունկցիայի ինտեգրալը, ապա սա նույնպես պետք է սահմանել
(defun f (x) (- (* x x) (* x 3)))
ապա
integral
ֆունկցիան կիրառել այս
f
ֆունկցիայի նկատմամբ։ Բայց
անանուն ֆունկցիաների մեխանիզմը հնարավորություն է տալիս խուսափել ինտեգրվող ֆունկցիայի առանձին սահմանումից։ Օրինակ, այս վեջին ֆունկցիան
[−2;1] միջակայքում ինտեգրելու համար կարելի է գրել.
(integral #'(lambda (x) (- (* x x) (* x 3))) -2 1)
Այստեղ
integral
ֆունկցիայի կանչի մեջ
lambda
մակրոսով ստեղծվել է ինտեգրվող ֆունկցիային համապատասխան անանուն ֆունկցիա։
C++11 լեզվում նույնպես կարելի է գրել համարժեք արտահայտություն.
integral([](double x)->double{ return x*x-3*x;}, -2, 1);
որտեղ անանուն ֆունկցիան սահմանված C++11 լեզվի
[]()->{}
կառուցվածքով։
Բայց սեղանների մեթոդը ինտեգրալի հաշվման միակ մեթոդը չէ։ Օրինակ,
f ֆունկցիան
[a;b] հատվածի վրա կարելի է մոտարկել ոչ թե ուղիղ գծով, այլ պարաբոլով։ Այս մեթոդը կոչվում է
Սիմպսոնի մեթոդ և ներկայանում է հետևյալ բանաձևով.
simpson(f,a,b)=b−a6(f(a)+4f(a+b2)+f(b))
Եվ ինտեգրալի հաշվման թվային մեթոդը նույնպես կարելի է տալ
integral
ֆունկցիային որպես արգումենտ։ Այդ դեպքում կստանանք մի նոր,
երրորդ կարգի ֆունկցիա.
integral(method,f,a,b,ε)={method(f,a,b),|b−a|≤ε,integral(f,a,a+b2,ε)+integral(f,a+b2,b,ε),|b−a|>ε.
Ծրագրավորենք այս ֆունկցիան Common Lisp լեզվով.
(defun integral (method f a b &optional (eps 0.001))
(if (< (- b a) eps)
(funcall method f a b)
(let ((m (/ (+ a b) 2)))
(+ (integral method f a m eps)
(integral method f m b eps)))))
Եվ C++11 լեզվով։
using method = std::function<double(mathfunc,double,double)>>;
double integral( method r, mathfunc f, double a, double b, double delta = 0.001 )
{
if( b - a < delta )
return r(f, a, b);
auto m((a + b) / 2);
return integral(r, f, a, m, delta) + integral(r, f, m, b, delta);
}