دالة من الدرجة العليا
في الرياضيات وعلم الحاسوب، تُعدّ الدالة من المرتبة الأعلى أو الدالة من الدرجة العليا (بالإنجليزية: Higher-order function) التي تُعرف اختصارًا بـ HOF، هي دالة تقوم على الأقل بأحد الأمور التالية:
- تأخذ دالة أو أكثر كوسيط (أي معامل إجرائي، وهو وسيط في الدالة يكون بحد ذاته إجراء)،
- تُرجع دالة كنتيجة.
جميع الدوال الأخرى تُعدّ دوال من المرتبة الأولى. في الرياضيات، تُسمى الدوال من المرتبة الأعلى أيضًا بالمؤثر أو الدالي. ويُعدّ المؤثر التفاضلي في التفاضل والتكامل مثالًا شائعًا، لأنه يُحوّل دالة إلى مشتقِ لها، وهي أيضًا دالة. لا ينبغي الخلط بين الدوال من المرتبة الأعلى وغيرها من الاستخدامات لكلمة "المؤثر" في الرياضيات، انظر دال.
في تكامل لامدا غير المُنمَّط، تكون جميع الدوال من المرتبة الأعلى؛ أما في تفاضل لامدا النمطي، الذي تشتق منه معظم لغات برمجة وظيفية، فإن الدوال من المرتبة الأعلى التي تأخذ دالة كوسيط تكون لها أنواع بالقالب التالي: .
أمثلة عامة
- دالة
map، الموجودة في العديد من لغات البرمجة الوظيفية، تُعد مثالًا على دالة من المرتبة الأعلى. فهي تأخذ دالة f ومجموعة من العناصر كوسيطين، وتُرجع نتيجةً هي مجموعة جديدة تحتوي على f مطبّقة على كل عنصر من عناصر المجموعة الأصلية.
- دوال الترتيب (بالإنجليزية: sorting functions)، التي تأخذ دالة مقارنة كوسيط، مما يتيح للمبرمج فصل خوارزمية الترتيب عن طريقة مقارنة العناصر. وتُعد الدالة القياسية
qsortفي لغة سي مثالًا على ذلك.
- التصفية
- الطي
- المجموع التراكمي
- التطبيث
- تركيب الدوال
- التكامل
- رد النداء
- اجتياز هيكلة الشجرة
- نحو مونتاغيو، وهي نظرية دلالية للغة الطبيعية، تستخدم الدوال من المرتبة الأعلى.
الدعم في لغات البرمجة
الدعم المباشر
الأمثلة الواردة ليست بغرض المقارنة بين لغات البرمجة، وإنما لتوضيح بنية دوال المرتبة الأعلى فيها
في الأمثلة التالية، تقوم الدالة من المرتبة الأعلى twice بأخذ دالة وتطبيقها على قيمة معينة مرتين. وإذا كان من الضروري تطبيق twice عدة مرات على نفس الدالة f، فمن الأفضل أن تُرجع دالة بدلاً من قيمة. وهذا يتماشى مع مبدأ "لا تكرر نفسك".
إيه بي إل
twice←{⍺⍺ ⍺⍺ ⍵}
plusthree←{⍵+3}
g←{plusthree twice ⍵}
g 7
13
أو بطريقة ضمنية:
twice←⍣2
plusthree←+∘3
g←plusthree twice
g 7
13
سي++
يُستخدم std::function في سي++11:
#include <iostream>
#include <functional>
auto twice = [](const std::function<int(int)>& f)
{
return [f](int x) {
return f(f(x));
};
};
auto plus_three = [](int i)
{
return i + 3;
};
int main()
{
auto g = twice(plus_three);
std::cout << g(7) << '\n'; // 13
}
أو، باستخدام لامبدا العامة التي يوفرها سي++14:
#include <iostream>
auto twice = [](const auto& f)
{
return [f](int x) {
return f(f(x));
};
};
auto plus_three = [](int i)
{
return i + 3;
};
int main()
{
auto g = twice(plus_three);
std::cout << g(7) << '\n'; // 13
}
سي شارب
باستخدام المفوضات فقط (بالإنجليزية: delegates):
using System;
public class Program
{
public static void Main(string[] args)
{
Func<Func<int, int>, Func<int, int>> twice = f => x => f(f(x));
Func<int, int> plusThree = i => i + 3;
var g = twice(plusThree);
Console.WriteLine(g(7)); // 13
}
}
أو بطريقة مكافئة، باستخدام دوال ثابتة (بالإنجليزية: static methods):
using System;
public class Program
{
private static Func<int, int> Twice(Func<int, int> f)
{
return x => f(f(x));
}
private static int PlusThree(int i) => i + 3;
public static void Main(string[] args)
{
var g = Twice(PlusThree);
Console.WriteLine(g(7)); // 13
}
}
كلوجر
(defn twice [f]
(fn [x] (f (f x))))
(defn plus-three [i]
(+ i 3))
(def g (twice plus-three))
(println (g 7)) ; 13
سي إف إم
twice = function(f) {
return function(x) {
return f(f(x));
};
};
plusThree = function(i) {
return i + 3;
};
g = twice(plusThree);
writeOutput(g(7)); // 13
كومون ليسب
(defun twice (f)
(lambda (x) (funcall f (funcall f x))))
(defun plus-three (i)
(+ i 3))
(defvar g (twice #'plus-three))
(print (funcall g 7))
دي
import std.stdio : writeln;
alias twice = (f) => (int x) => f(f(x));
alias plusThree = (int i) => i + 3;
void main()
{
auto g = twice(plusThree);
writeln(g(7)); // 13
}
دارت
int Function(int) twice(int Function(int) f) {
return (x) {
return f(f(x));
};
}
int plusThree(int i) {
return i + 3;
}
void main() {
final g = twice(plusThree);
print(g(7)); // 13
}
إليكسير
في إليكسير، يمكنك المزج بين تعريفات الوحدة والدالة المجهولة
defmodule Hof do
def twice(f) do
fn(x) -> f.(f.(x)) end
end
end
plus_three = fn(i) -> i + 3 end
g = Hof.twice(plus_three)
IO.puts g.(7) # 13
وبدلاً من ذلك، يمكننا أيضًا التأليف باستخدام وظائف مجهولة الهوية تمامًا.
twice = fn(f) ->
fn(x) -> f.(f.(x)) end
end
plus_three = fn(i) -> i + 3 end
g = twice.(plus_three)
IO.puts g.(7) # 13
إرلانج
or_else([], _) -> false;
or_else([F | Fs], X) -> or_else(Fs, X, F(X)).
or_else(Fs, X, false) -> or_else(Fs, X);
or_else(Fs, _, {false, Y}) -> or_else(Fs, Y);
or_else(_, _, R) -> R.
or_else([fun erlang:is_integer/1, fun erlang:is_atom/1, fun erlang:is_list/1], 3.23).
في هذا المثال بلغة Erlang، تقوم الدالة العليا or_else/2 بأخذ قائمة من الدوال (Fs) ومعامل (X). تقوم الدالة بتقييم الدالة F باستخدام X كمعامل. إذا أعادت الدالة F القيمة false، فسيتم تقييم الدالة التالية في القائمة Fs. إذا أعادت الدالة F القيمة {false, Y}، فسيتم تقييم الدالة التالية في القائمة Fs باستخدام Y كمعامل. إذا أعادت الدالة F القيمة R، فإن الدالة العليا or_else/2 ستُرجع R. لاحظ أن X وY وR يمكن أن تكون دوال. المثال يعيد القيمة false.
إف شارب
let twice f = f >> f
let plus_three = (+) 3
let g = twice plus_three
g 7 |> printf "%A" // 13
غو
package main
import "fmt"
func twice(f func(int) int) func(int) int {
return func(x int) int {
return f(f(x))
}
}
func main() {
plusThree := func(i int) int {
return i + 3
}
g := twice(plusThree)
fmt.Println(g(7)) // 13
}
لاحظ أن التعبير الحرفي للدالة يمكن تعريفه إما باستخدام معرف (twice) أو بشكل مجهول (ويُسند إلى متغير مثل plusThree).
أباتشي جروفي
def twice = { f, x -> f(f(x)) }
def plusThree = { it + 3 }
def g = twice.curry(plusThree)
println g(7) // 13
هاسكل
twice :: (Int -> Int) -> (Int -> Int)
twice f = f . f
plusThree :: Int -> Int
plusThree = (+3)
main :: IO ()
main = print (g 7) -- 13
where
g = twice plusThree
جيه
بشكلٍ واضح،
twice=. adverb : 'u u y'
plusthree=. verb : 'y + 3'
g=. plusthree twice
g 7
13
أو ضمنيًا،
twice=. ^:2
plusthree=. +&3
g=. plusthree twice
g 7
13
جافا
باستخدام واجهات الدوال فقط:
import java.util.function.*;
class Main {
public static void main(String[] args) {
Function<IntUnaryOperator, IntUnaryOperator> twice = f -> f.andThen(f);
IntUnaryOperator plusThree = i -> i + 3;
var g = twice.apply(plusThree);
System.out.println(g.applyAsInt(7)); // 13
}
}
أو بشكل مكافئ، باستخدام دوال ثابتة:
import java.util.function.*;
class Main {
private static IntUnaryOperator twice(IntUnaryOperator f) {
return f.andThen(f);
}
private static int plusThree(int i) {
return i + 3;
}
public static void main(String[] args) {
var g = twice(Main::plusThree);
System.out.println(g.applyAsInt(7)); // 13
}
}
جافا سكريبت
باستخدام دوال السهم (بالإنجليزية: arrow functions):
"use strict";
const twice = f => x => f(f(x));
const plusThree = i => i + 3;
const g = twice(plusThree);
console.log(g(7)); // 13
أو باستخدام الصيغة الكلاسيكية:
"use strict";
function twice(f) {
return function (x) {
return f(f(x));
};
}
function plusThree(i) {
return i + 3;
}
const g = twice(plusThree);
console.log(g(7)); // 13
جوليا
julia> function twice(f)
function result(x)
return f(f(x))
end
return result
end
twice (generic function with 1 method)
julia> plusthree(i) = i + 3
plusthree (generic function with 1 method)
julia> g = twice(plusthree)
(::var"#result#3"{typeof(plusthree)}) (generic function with 1 method)
julia> g(7)
13
كوتلن
fun twice(f: (Int) -> Int): (Int) -> Int {
return { f(f(it)) }
}
fun plusThree(i: Int) = i + 3
fun main() {
val g = twice(::plusThree)
println(g(7)) // 13
}
لوا
function twice(f)
return function (x)
return f(f(x))
end
end
function plusThree(i)
return i + 3
end
local g = twice(plusThree)
print(g(7)) -- 13
ماتلاب
function result = twice(f)
result = @(x) f(f(x));
end
plusthree = @(i) i + 3;
g = twice(plusthree)
disp(g(7)); % 13
أوكامل
let twice f x =
f (f x)
let plus_three =
(+) 3
let () =
let g = twice plus_three in
print_int (g 7); (* 13 *)
print_newline ()
بي إتش بي
<?php
declare(strict_types=1);
function twice(callable $f): Closure {
return function (int $x) use ($f): int {
return $f($f($x));
};
}
function plusThree(int $i): int {
return $i + 3;
}
$g = twice('plusThree');
echo $g(7), "\n"; // 13
أو باستخدام متغيرات لكل الدوال:
<?php
declare(strict_types=1);
$twice = fn(callable $f): Closure => fn(int $x): int => $f($f($x));
$plusThree = fn(int $i): int => $i + 3;
$g = $twice($plusThree);
echo $g(7), "\n"; // 13
لاحظ أن دوال السهم (بالإنجليزية: arrow functions) تقوم ضمنيًا بالتقاط أي متغيرات من النطاق الأبوي،[1] بينما تتطلب الدوال المجهولة استخدام الكلمة المفتاحية use للقيام بذلك.
بيرل
use strict;
use warnings;
sub twice {
my ($f) = @_;
sub {
$f->($f->(@_));
};
}
sub plusThree {
my ($i) = @_;
$i + 3;
}
my $g = twice(\&plusThree);
print $g->(7), "\n"; # 13
أو باستخدام جميع الدوال في متغيرات:
use strict;
use warnings;
my $twice = sub {
my ($f) = @_;
sub {
$f->($f->(@_));
};
};
my $plusThree = sub {
my ($i) = @_;
$i + 3;
};
my $g = $twice->($plusThree);
print $g->(7), "\n"; # 13
بايثون
>>> def twice(f):
... def result(x):
... return f(f(x))
... return result
>>> plus_three = lambda i: i + 3
>>> g = twice(plus_three)
>>> g(7)
13
غالبًا ما يُستخدم بناء جملة مُزيّن بايثون لاستبدال دالة بنتيجة تمريرها عبر دالة من رتبة أعلى. على سبيل المثال، يمكن تنفيذ الدالة g بشكل مكافئ:
>>> @twice
... def g(i):
... return i + 3
>>> g(7)
13
آر
twice <- \(f) \(x) f(f(x))
plusThree <- function(i) i + 3
g <- twice(plusThree)
> g(7)
[1] 13
راكو
sub twice(Callable:D $f) {
return sub { $f($f($^x)) };
}
sub plusThree(Int:D $i) {
return $i + 3;
}
my $g = twice(&plusThree);
say $g(7); # 13
في راكو، جميع كائنات الكود هي إغلاقات، وبالتالي يمكنها الإشارة إلى متغيرات "معجمية" داخلية من نطاق خارجي لأن المتغير المعجمي "مغلق" داخل الدالة. يدعم راكو أيضًا صيغة "الكتلة المدببة" لتعبيرات لامدا، والتي يمكن تخصيصها لمتغير أو استدعاؤها بشكل مجهول.
روبي
def twice(f)
->(x) { f.call(f.call(x)) }
end
plus_three = ->(i) { i + 3 }
g = twice(plus_three)
puts g.call(7) # 13
رست
fn twice(f: impl Fn(i32) -> i32) -> impl Fn(i32) -> i32 {
move |x| f(f(x))
}
fn plus_three(i: i32) -> i32 {
i + 3
}
fn main() {
let g = twice(plus_three);
println!("{}", g(7)) // 13
}
سكالا
object Main {
def twice(f: Int => Int): Int => Int =
f compose f
def plusThree(i: Int): Int =
i + 3
def main(args: Array[String]): Unit = {
val g = twice(plusThree)
print(g(7)) // 13
}
}
سكيم
(define (compose f g)
(lambda (x) (f (g x))))
(define (twice f)
(compose f f))
(define (plus-three i)
(+ i 3))
(define g (twice plus-three))
(display (g 7)) ; 13
(display "\n")
سويفت
func twice(_ f: @escaping (Int) -> Int) -> (Int) -> Int {
return { f(f($0)) }
}
let plusThree = { $0 + 3 }
let g = twice(plusThree)
print(g(7)) // 13
تي سي إل
set twice {{f x} {apply $f [apply $f $x]}}
set plusThree {{i} {return [expr $i + 3]}}
# result: 13
puts [apply $twice $plusThree 7]
تستخدم تي سي إل الأمر "apply" لتطبيق دالة مجهولة (منذ الإصدار 8.6).
إكس إيه سي إم إل
يعرّف معيار إكس إيه سي إم إل (بالإنجليزية: XACML) دوالًا من المرتبة الأعلى ضمن المعيار لتطبيق دالة على عدة قيم من مجموعات السمات.
rule allowEntry{
permit
condition anyOfAny(function[stringEqual], citizenships, allowedCitizenships)
}
إكس كويري
declare function local:twice($f, $x) {
$f($f($x))
};
declare function local:plusthree($i) {
$i + 3
};
local:twice(local:plusthree#1, 7) (: 13 :)
البدائل
مؤشرات الدوال
تسمح مؤشرات الدوال في لغات مثل سي، سي++، فورتران، وباسكال للمبرمجين بتمرير مراجع إلى الدوال. الكود التالي في لغة C يحسب تقريبًا للتكامل لدالة عشوائية:
#include <stdio.h>
double square(double x)
{
return x * x;
}
double cube(double x)
{
return x * x * x;
}
/* حساب التكامل لدالة f() ضمن الفاصل الزمني [a,b] */
double integral(double f(double x), double a, double b, int n)
{
int i;
double sum = 0;
double dt = (b - a) / n;
for (i = 0; i < n; ++i) {
sum += f(a + (i + 0.5) * dt);
}
return sum * dt;
}
int main()
{
printf("%g\n", integral(square, 0, 1, 100));
printf("%g\n", integral(cube, 0, 1, 100));
return 0;
}
دالة qsort من مكتبة C القياسية تستخدم مؤشر دالة لمحاكاة سلوك دالة من المرتبة الأعلى.
الماكرو
يمكن أيضًا استخدام الماكرو لتحقيق بعض تأثيرات الدوال من المرتبة الأعلى. ومع ذلك، لا يمكن للماكرو بسهولة تجنب مشكلة التقاط المتغيرات (بالإنجليزية: variable capture)؛ وقد يؤدي أيضًا إلى توليد كميات كبيرة من الشيفرة المكررة، مما قد يصعب على المُصرّف تحسينها. وبشكل عام، لا تكون الماكروات مُنظمة بنظام أنواع قوي، على الرغم من أنها قد تُنتج شيفرة تعتمد على نظام أنواع قوي.
تقييم الشيفرة ديناميكيًا
في بعض لغات البرمجة الأمرية، يمكن تحقيق بعض النتائج الخوارزمية نفسها التي يتم الوصول إليها باستخدام الدوال من المرتبة الأعلى، وذلك عن طريق تنفيذ الشيفرة ديناميكيًا (ويُشار إلى هذه العملية أحيانًا باسم "Eval" أو "Execute") ضمن نطاق التقييم. ومع ذلك، هناك عيوب كبيرة محتملة لهذا النهج:
- عادةً لا يكون الكود المُمرَّر كوسيط نظامي النوع؛ حيث تعتمد هذه اللغات بشكل عام على نظام الأنواع للتحقق من صحة الشيفرة وسلامتها.
- غالبًا ما يُقدَّم الوسيط على شكل سلسلة نصية، قد لا يكون معروفًا محتواها إلا أثناء وقت التشغيل. ويجب ترجمة هذه السلسلة إما أثناء تنفيذ البرنامج (باستخدام الترجمة في الوقت المناسب) أو تقييمها باستخدام المفسر. وهذا يؤدي إلى زيادة في العبء على وقت التشغيل، وغالبًا ما ينتج عنه كود أقل كفاءة.
الكائنات
في لغات البرمجة كائنية التوجه التي لا تدعم الدوال من المرتبة الأعلى، يمكن أن يكون الكائن بديلاً فعالًا. إذ أن الطريقة الخاصة بالكائن تعمل بشكل جوهري كالدوال، ويمكن للطريقة أن تقبل كائنات كوسائط وأن تُنتج كائنات كقيمة مُعادة. ومع ذلك، غالبًا ما تُضيف الكائنات عبئًا إضافيًا في وقت التشغيل مقارنةً بالدوال البحتة، بالإضافة إلى المزيد من الشفرة المتداولة لتعريف الكائن وإنشاء نسخة منه مع الطريقة (أو الطرق) الخاصة به. تتيح اللغات التي تدعم كائنات مخصصة للتخزين في المكدس (بدلاً من الاعتماد على إدارة الذاكرة) أو السجلات مزيدًا من المرونة عند استخدام هذا الأسلوب.
مثال على استخدام سجل بسيط مخزَّن في المكدس في فري باسكال مع دالة تُعيد دالة:
program example;
type
int = integer;
Txy = record x, y: int; end;
Tf = function (xy: Txy): int;
function f(xy: Txy): int;
begin
Result := xy.y + xy.x;
end;
function g(func: Tf): Tf;
begin
result := func;
end;
var
a: Tf;
xy: Txy = (x: 3; y: 7);
begin
a := g(@f); // تعيين الدالة المُعادة إلى "a"
writeln(a(xy)); // يطبع 10
end.
تقوم الدالة a() بأخذ سجل من النوع Txy كوسيط وتُعيد القيمة الصحيحة (عدد صحيح) الناتجة من جمع الحقلين x وy في السجل (3 + 7).
إلغاء التفعيلية
يمكن استخدام إلغاء التفعيلية لتنفيذ الدوال من المرتبة الأعلى في لغات البرمجة التي لا تدعم الدوال من الدرجة الأولى:
// هياكل بيانات الدوال التي تم إلغاء تفعيلها
template<typename T> struct Add { T value; };
template<typename T> struct DivBy { T value; };
template<typename F, typename G> struct Composition { F f; G g; };
// تنفيذ تطبيقات الدوال التي تم إلغاء تفعيلها
template<typename F, typename G, typename X>
auto apply(Composition<F, G> f, X arg) {
return apply(f.f, apply(f.g, arg));
}
template<typename T, typename X>
auto apply(Add<T> f, X arg) {
return arg + f.value;
}
template<typename T, typename X>
auto apply(DivBy<T> f, X arg) {
return arg / f.value;
}
// دالة التركيب من الدرجة الأعلى
template<typename F, typename G>
Composition<F, G> compose(F f, G g) {
return Composition<F, G> {f, g};
}
int main(int argc, const char* argv[]) {
auto f = compose(DivBy<float>{ 2.0f }, Add<int>{ 5 });
apply(f, 3); // 4.0f
apply(f, 9); // 7.0f
return 0;
}
في هذا المثال، يتم استخدام أنواع مختلفة لتفعيل دوال مختلفة عبر تعدد أشكال الدوال. الدالة التي تم تطبيق تعدد الأشكال عليها في هذا المثال تحمل التوقيع auto apply.
انظر أيضًا
المراجع
- ↑ "PHP: Arrow Functions - Manual". www.php.net. مؤرشف من الأصل في 2025-06-19. اطلع عليه بتاريخ 2021-03-01.