Funcție de ordin superior - Higher-order function

În matematică și informatică , o funcție de ordin superior este o funcție care face cel puțin una dintre următoarele:

  • ia una sau mai multe funcții ca argumente (adică parametrii procedurali ),
  • returnează o funcție ca rezultat.

Toate celelalte funcții sunt funcții de prim ordin . În matematică funcțiile de ordin superior sunt, de asemenea, denumite operatori sau funcționale . Operatorul diferențialului în calcul este un exemplu comun, deoarece mapează o funcție a acestuia derivat , de asemenea , o funcție. Funcțiile de ordin superior nu trebuie confundate cu alte utilizări ale cuvântului „functor” de-a lungul matematicii, vezi Functor (dezambiguizare) .

În calculul lambda netipat , toate funcțiile sunt de ordin superior; într-un calcul lambda tastat , din care sunt derivate majoritatea limbajelor de programare funcționale , funcțiile de ordin superior care iau o funcție ca argument sunt valori cu tipuri de formă .

Exemple generale

  • mapfuncția, găsită în multe limbaje funcționale de programare, este un exemplu de funcție de ordin superior. Ia ca argumente o funcție f și o colecție de elemente și, ca rezultat, returnează o nouă colecție cu f aplicată fiecărui element din colecție.
  • Funcții de sortare, care iau o funcție de comparație ca parametru, permițând programatorului să separe algoritmul de sortare de comparațiile articolelor care sunt sortate. C standard , funcția qsort este un exemplu în acest sens.
  • filtru
  • plia
  • aplica
  • Compoziția funcției
  • Integrare
  • Suna inapoi
  • Traversarea copacilor
  • Gramatica Montague , o teorie semantică a limbajului natural, folosește funcții de ordin superior

Suport în limbaje de programare

Suport direct

Exemplele nu sunt destinate comparării și contrastării limbajelor de programare, ci pentru a servi ca exemple de sintaxă a funcțiilor de ordin superior

În exemplele următoare, funcția de ordin superior twiceia o funcție și aplică funcția la o anumită valoare de două ori. Dacă twicetrebuie aplicat de mai multe ori pentru același flucru, de preferință ar trebui să returneze o funcție mai degrabă decât o valoare. Acest lucru este în conformitate cu principiul „ nu te repeta ”.

APL

      twice{⍺⍺ ⍺⍺ }

      plusthree{+3}

      g{plusthree twice }
    
      g 7
13

Sau într-un mod tacit:

      twice2

      plusthree+3

      gplusthree twice
    
      g 7
13

C ++

Utilizarea std::functionîn C ++ 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
}

Sau, cu lambdas generice furnizate de C ++ 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
}

C #

Folosind doar delegați:

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

Sau echivalent, cu metode statice:

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

Clojure

(defn twice [f]
  (fn [x] (f (f x))))

(defn plus-three [i]
  (+ i 3))

(def g (twice plus-three))

(println (g 7)) ; 13

Limbaj de marcare ColdFusion (CFML)

twice = function(f) {
    return function(x) {
        return f(f(x));
    };
};

plusThree = function(i) {
    return i + 3;
};

g = twice(plusThree);

writeOutput(g(7)); // 13

Lisp comun

(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))

D

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
}

Lance

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
}

Elixir

În Elixir, puteți amesteca definiții ale modulelor și funcții anonime

defmodule Hof do
    def twice(f) do
        fn(x) -> f.(f.(x)) end
    end
end

plus_three = fn(i) -> 3 + i end

g = Hof.twice(plus_three)

IO.puts g.(7) # 13

Alternativ, putem compune și folosind funcții anonime pure.

twice = fn(f) ->
    fn(x) -> f.(f.(x)) end
end

plus_three = fn(i) -> 3 + i end

g = twice.(plus_three)

IO.puts g.(7) # 13

Erlang

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

În acest exemplu Erlang, funcția de ordin superior or_else/2ia o listă de funcții ( Fs) și argument ( X). Evaluează funcția Fcu argumentul Xca argument. Dacă funcția Freturnează fals atunci Fsva fi evaluată următoarea funcție din . Dacă funcția Frevine, va fi evaluată {false, Y}următoarea funcție din Fsargument Y. Dacă funcția Frevine, funcția Rde ordin superior or_else/2va reveni R. Rețineți că X,, Yși Rpot fi funcții. Exemplul revine false.

F #

let twice f = f >> f

let plus_three = (+) 3

let g = twice plus_three

g 7 |> printf "%A" // 13

Merge

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
}

Observați că o funcție literală poate fi definită fie cu un identificator ( twice), fie anonim (atribuit variabilei plusThree).

Haskell

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

J

Explicit,

   twice=.     adverb : 'u u y'

   plusthree=. verb   : 'y + 3'
   
   g=. plusthree twice
   
   g 7
13

sau tacit,

   twice=. ^:2

   plusthree=. +&3
   
   g=. plusthree twice
   
   g 7
13

Java (1.8+)

Folosind doar interfețe funcționale:

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

Sau echivalent, cu metode statice:

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

JavaScript

"use strict";

const twice = f => x => f(f(x));

const plusThree = i => i + 3;

const g = twice(plusThree);

console.log(g(7)); // 13

Julia

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

Kotlin

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
}

Lua

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

MATLAB

function result = twice(f)
    result = @inner

    function val = inner(x)
        val = f(f(x));
    end
end

plusthree = @(i) i + 3;

g = twice(plusthree)

disp(g(7)); % 13

OCaml

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

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

sau cu toate funcțiile în variabile:

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

Rețineți că funcțiile săgeată captează implicit orice variabilă care provine din domeniul de aplicare părinte, în timp ce funcțiile anonime necesită usecuvântul cheie să facă același lucru.

Perl

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

sau cu toate funcțiile în variabile:

use strict;
use warnings;

my $twice = sub {
    my ($f) = @_;
    sub {
        $f->($f->(@_));
    };
};

my $plusThree = sub {
    my ($x) = @_;
    $x + 3;
};

my $g = $twice->($plusThree);

print $g->(7), "\n"; # 13

Piton

>>> def twice(f):
...     def result(x):
...         return f(f(x))
...     return result

>>> plusthree = lambda i: i + 3

>>> g = twice(plusthree)
    
>>> g(7)
13

Sintaxa decoratorului Python este adesea utilizată pentru a înlocui o funcție cu rezultatul trecerii acelei funcții printr-o funcție de ordin superior. De exemplu, funcția gar putea fi implementată în mod echivalent:

>>> @twice
... def g(i):
...     return i + 3

>>> g(7)
13

R

twice <- function(f) {
  return(function(x) {
    f(f(x))
  })
}

plusThree <- function(i) {
  return(i + 3)
}

g <- twice(plusThree)

> print(g(7))
[1] 13

Raku

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

În Raku, toate obiectele de cod sunt închideri și, prin urmare, pot face referință la variabilele "lexicale" interioare dintr-un domeniu de aplicare extern, deoarece variabila lexicală este "închisă" în interiorul funcției. Raku acceptă, de asemenea, sintaxa „bloc punctat” pentru expresiile lambda care pot fi atribuite unei variabile sau invocate anonim.

Rubin

def twice(f)
  ->(x) { f.call f.call(x) }
end

plus_three = ->(i) { i + 3 }

g = twice(plus_three)

puts g.call(7) # 13

Rugini

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
}

Scala

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

Sistem

(define (add x y) (+ x y))
(define (f x)
  (lambda (y) (+ x y)))
(display ((f 3) 7))
(display (add 3 7))

În acest exemplu de schemă, funcția de ordin superior (f x)este utilizată pentru a implementa currying . Este nevoie de un singur argument și returnează o funcție. Evaluarea expresiei ((f 3) 7)returnează mai întâi o funcție după evaluare (f 3). Funcția returnată este (lambda (y) (+ 3 y)). Apoi, evaluează funcția returnată cu 7 ca argument, returnând 10. Aceasta este echivalentă cu expresia (add 3 7), deoarece (f x)este echivalentă cu forma curri a lui (add x y).

Rapid

func twice(_ f: @escaping (Int) -> Int) -> (Int) -> Int {
    return { f(f($0)) }
}

let plusThree = { $0 + 3 }

let g = twice(plusThree)

print(g(7)) // 13

Tcl

set twice {{f x} {apply $f [apply $f $x]}}
set plusThree {{i} {return [expr $i + 3]}}

# result: 13
puts [apply $twice $plusThree 7]

Tcl folosește comanda apply pentru a aplica o funcție anonimă (de la 8.6).

XACML

Standardul XACML definește funcții de ordin superior în standard pentru a aplica o funcție valorilor multiple ale pungilor de atribute.

rule allowEntry{
    permit
    condition anyOfAny(function[stringEqual], citizenships, allowedCitizenships)
}

Lista funcțiilor de ordin superior din XACML poate fi găsită aici .

XQuery

declare function local:twice($f, $x) {
  $f($f($x))
};

declare function local:plusthree($i) {
  $i + 3
};

local:twice(local:plusthree#1, 7) (: 13 :)

Alternative

Indicații funcționale

Indicatorii de funcții în limbaje precum C , C ++ și Pascal permit programatorilor să transmită referințe la funcții. Următorul cod C calculează o aproximare a integralei unei funcții arbitrare:

#include <stdio.h>

double square(double x)
{
    return x * x;
}

double cube(double x)
{
    return x * x * x;
}

/* Compute the integral of f() within the interval [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;
}

Funcția qsort din biblioteca standard C utilizează un indicator de funcție pentru a emula comportamentul unei funcții de ordin superior.

Macrocomenzi

Macro-urile pot fi, de asemenea, utilizate pentru a obține unele dintre efectele funcțiilor de ordin superior. Cu toate acestea, macrocomenzile nu pot evita cu ușurință problema capturării variabilei; acestea pot duce, de asemenea, la cantități mari de cod duplicat, ceea ce poate fi mai dificil de optimizat pentru un compilator. În general, macro-urile nu sunt puternic tastate, deși pot produce cod puternic tastat.

Evaluare dinamică a codului

În alte limbaje de programare imperative , este posibil să se obțină aceleași rezultate algoritmice ca și cele obținute prin funcții de ordin superior prin executarea dinamică a codului (uneori numit operații Eval sau Execute ) în domeniul evaluării. Această abordare poate avea dezavantaje semnificative:

  • Codul argumentului care trebuie executat nu este de obicei scris tipic ; aceste limbi se bazează, în general, pe tastarea dinamică pentru a determina formarea și siguranța codului care trebuie executat.
  • Argumentul este de obicei furnizat ca un șir, a cărui valoare poate să nu fie cunoscută decât în ​​timpul rulării. Acest șir trebuie fie compilat în timpul execuției programului (utilizând o compilare just-in-time ), fie evaluat prin interpretare , provocând unele cheltuieli suplimentare în timpul rulării și, de obicei, generând cod mai puțin eficient.

Obiecte

În limbajele de programare orientate obiect care nu acceptă funcții de ordin superior, obiectele pot fi un substitut eficient. Metodele unui obiect acționează în esență, precum funcțiile, iar o metodă poate accepta obiecte ca parametri și produce obiecte ca valori returnate. Obiectele poartă deseori un timp de execuție adăugat în comparație cu funcțiile pure, totuși și au adăugat un cod boilerplate pentru definirea și instanțierea unui obiect și a metodei acestuia. Limbile care permit obiecte sau structuri bazate pe stivă (versus stivă ) pot oferi mai multă flexibilitate cu această metodă.

Un exemplu de utilizare a unei înregistrări simple bazate pe stivă în Free Pascal cu o funcție care returnează o funcție:

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);     // return a function to "a"
  writeln(a(xy)); // prints 10
end.

Funcția a()ia o Txyînregistrare ca intrare și returnează valoarea întreagă a sumei înregistrării xși a ycâmpurilor (3 + 7).

Defuncționalizare

Defuncționalizarea poate fi utilizată pentru a implementa funcții de ordin superior în limbi care nu au funcții de primă clasă :

// Defunctionalized function data structures
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; };

// Defunctionalized function application implementations
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;
}

// Higher-order compose function
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;
}

În acest caz, diferite tipuri sunt utilizate pentru a declanșa funcții diferite prin supraîncărcarea funcției . Funcția supraîncărcată din acest exemplu are semnătura auto apply.

Vezi si

Referințe