دالة من الدرجة العليا

في الرياضيات وعلم الحاسوب، تُعدّ الدالة من المرتبة الأعلى أو الدالة من الدرجة العليا  التي تُعرف اختصارًا بـ HOF، هي دالة تقوم على الأقل بأحد الأمور التالية:
 تأخذ دالة أو أكثر كوسيط (أي معامل إجرائي، وهو وسيط في الدالة يكون بحد ذاته إجراء)،
 تُرجع دالة كنتيجة.

جميع الدوال الأخرى تُعدّ دوال من المرتبة الأولى. في الرياضيات، تُسمى الدوال من المرتبة الأعلى أيضًا بالمؤثر أو الدالي. ويُعدّ المؤثر التفاضلي في التفاضل والتكامل مثالًا شائعًا، لأنه يُحوّل دالة إلى مشتقِ لها، وهي أيضًا دالة. لا ينبغي الخلط بين الدوال من المرتبة الأعلى وغيرها من الاستخدامات لكلمة "المؤثر" في الرياضيات، انظر دال.

في تكامل لامدا غير المُنمَّط، تكون جميع الدوال من المرتبة الأعلى؛ أما في تفاضل لامدا النمطي، الذي تشتق منه معظم لغات برمجة وظيفية، فإن الدوال من المرتبة الأعلى التي تأخذ دالة كوسيط تكون لها أنواع بالقالب التالي: .

 أمثلة عامة 

 دالة map، الموجودة في العديد من لغات البرمجة الوظيفية، تُعد مثالًا على دالة من المرتبة الأعلى. فهي تأخذ دالة f ومجموعة من العناصر كوسيطين، وتُرجع نتيجةً هي مجموعة جديدة تحتوي على f مطبّقة على كل عنصر من عناصر المجموعة الأصلية.

 دوال الترتيب ، التي تأخذ دالة مقارنة كوسيط، مما يتيح للمبرمج فصل خوارزمية الترتيب عن طريقة مقارنة العناصر. وتُعد الدالة القياسية qsort في لغة سي مثالًا على ذلك.

 التصفية
 الطي
 المجموع التراكمي
 التطبيث
 تركيب الدوال
 التكامل
 رد النداء
 اجتياز هيكلة الشجرة
 نحو مونتاغيو، وهي نظرية دلالية للغة الطبيعية، تستخدم الدوال من المرتبة الأعلى.

 الدعم في لغات البرمجة 

 الدعم المباشر 
الأمثلة الواردة ليست بغرض المقارنة بين لغات البرمجة، وإنما لتوضيح بنية دوال المرتبة الأعلى فيها

في الأمثلة التالية، تقوم الدالة من المرتبة الأعلى  بأخذ دالة وتطبيقها على قيمة معينة مرتين. وإذا كان من الضروري تطبيق  عدة مرات على نفس الدالة ، فمن الأفضل أن تُرجع دالة بدلاً من قيمة. وهذا يتماشى مع مبدأ "لا تكرر نفسك".

 إيه بي إل 

      twice←{⍺⍺ ⍺⍺ ⍵}

      plusthree←{⍵+3}

      g←{plusthree twice ⍵}

      g 7
13

أو بطريقة ضمنية:

      twice←⍣2

      plusthree←+∘3

      g←plusthree twice

      g 7
13

 سي++ 

يُستخدم  في سي++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
}

 سي شارب 

باستخدام المفوضات فقط :

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
    }
}

أو بطريقة مكافئة، باستخدام دوال ثابتة :

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، تقوم الدالة العليا  بأخذ قائمة من الدوال () ومعامل (). تقوم الدالة بتقييم الدالة  باستخدام  كمعامل. إذا أعادت الدالة  القيمة 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
}

لاحظ أن التعبير الحرفي للدالة يمكن تعريفه إما باستخدام معرف () أو بشكل مجهول (ويُسند إلى متغير مثل ).

 أباتشي جروفي 

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
    }
}

 جافا سكريبت 

باستخدام دوال السهم :

"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

لاحظ أن دوال السهم  تقوم ضمنيًا بالتقاط أي متغيرات من النطاق الأبوي، بينما تتطلب الدوال المجهولة استخدام الكلمة المفتاحية  للقيام بذلك.

 بيرل 

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
غالبًا ما يُستخدم بناء جملة مُزيّن بايثون لاستبدال دالة بنتيجة تمريرها عبر دالة من رتبة أعلى. على سبيل المثال، يمكن تنفيذ الدالة  بشكل مكافئ:

>>> @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).

 إكس إيه سي إم إل 

يعرّف معيار إكس إيه سي إم إل  دوالًا من المرتبة الأعلى ضمن المعيار لتطبيق دالة على عدة قيم من مجموعات السمات.

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 القياسية تستخدم مؤشر دالة لمحاكاة سلوك دالة من المرتبة الأعلى.

 الماكرو 
يمكن أيضًا استخدام الماكرو لتحقيق بعض تأثيرات الدوال من المرتبة الأعلى. ومع ذلك، لا يمكن للماكرو بسهولة تجنب مشكلة التقاط المتغيرات ؛ وقد يؤدي أيضًا إلى توليد كميات كبيرة من الشيفرة المكررة، مما قد يصعب على المُصرّف تحسينها. وبشكل عام، لا تكون الماكروات مُنظمة بنظام أنواع قوي، على الرغم من أنها قد تُنتج شيفرة تعتمد على نظام أنواع قوي.

 تقييم الشيفرة ديناميكيًا 
في بعض لغات البرمجة الأمرية، يمكن تحقيق بعض النتائج الخوارزمية نفسها التي يتم الوصول إليها باستخدام الدوال من المرتبة الأعلى، وذلك عن طريق تنفيذ الشيفرة ديناميكيًا (ويُشار إلى هذه العملية أحيانًا باسم "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.

 انظر أيضًا 
 برمجة وظيفية
 نموذج الإستراتيجية

 المراجع 

تصنيف:برمجة وظيفية
تصنيف:حسابات اللامدا
تصنيف:الوظائف الفرعية