Funkcióösszetétel (informatika) - Function composition (computer science)

A számítógép-tudomány , a funkció készítmény olyan cselekmény vagy mechanizmus kombinálni egyszerű funkciókat építeni bonyolultabb is. Mint a szokásos készítmény funkciók a matematika , az eredmény az egyes funkciók átadásra az érvelés a következő, és az eredmény az utolsó pedig a végeredményt.

A programozók gyakran más funkciók eredményeire is alkalmazzák a funkciókat, és szinte az összes programozási nyelv ezt lehetővé teszi. Bizonyos esetekben a függvények összetétele önmagában is érdekes, később felhasználható. Egy ilyen függvény mindig meghatározható, de az első osztályú funkciókkal rendelkező nyelvek megkönnyítik.

Az a képesség, hogy könnyen levélírás funkciók ösztönzi faktoring (széttöredeznének) funkciót a karbantarthatóság és a kód újrafelhasználásának . Általánosságban elmondható, hogy nagy rendszereket egész programok összeállításával lehet felépíteni.

Szűkebb értelemben a függvényösszetétel azokra a funkciókra vonatkozik, amelyek véges mennyiségű adattal működnek, és minden lépés egymás után feldolgozza, mielőtt átadná a következőnek. Azok a funkciók, amelyek potenciálisan végtelen adatokon (egy adatfolyam vagy más kodata ) működnek, szűrőként ismertek , és ehelyett egy folyamatban vannak összekötve , ami analóg a funkciók összetételével és egyidejűleg is végrehajtható .

Funkcióhívások írása

Tegyük fel például, hogy két f és g függvényünk van , mint például z = f ( y ) és y = g ( x ) . Összeállításuk azt jelenti, hogy először kiszámoljuk az y = g ( x ) értéket , majd y segítségével kiszámoljuk a z = f ( y ) értéket . Íme a példa a C nyelven :

float x, y, z;
// ...
y = g(x);
z = f(y);

A lépések kombinálhatók, ha nem adunk nevet a köztes eredménynek:

z = f(g(x));

A hosszbeli különbségek ellenére ez a két megvalósítás ugyanazt az eredményt számítja ki. A második megvalósítás csak egy kódsort igényel, és köznyelven "erősen összetett" formának nevezik. Az olvashatóság és ennélfogva a karbantarthatóság az egyik előnye a nagyon összetett űrlapoknak, mivel kevesebb kódsorra van szükségük, ezzel minimalizálva a program "felületét". DeMarco és Lister empirikusan igazolja a felület és a karbantarthatóság közötti inverz kapcsolatot. Másrészt lehetséges lehet a túlságosan összetett formák túlzott használata. A túl sok funkció beágyazása ellenkező hatást eredményezhet, így a kód kevésbé karbantartható.

Egy halom alapú nyelv , funkcionális készítmény még természetes: végezzük összefűzés , és általában az elsődleges módja a programok tervezését. A fenti példa a Forth-ban :

g f

Ami azt veszi, ami korábban volt a veremben, alkalmazza g-t, majd f-t, és az eredményt a veremen hagyja. A megfelelő matematikai jelölést lásd a postfix kompozíció jelölésében.

A függvények összetételének megnevezése

Tegyük fel most, hogy az f () meghívásának kombinációja g () eredményén gyakran hasznos, és amelyet a foo () néven szeretnénk megnevezni, hogy önálló függvényként használjuk.

A legtöbb nyelvben meghatározhatunk egy új, kompozícióval megvalósított függvényt. Példa a C-ben :

float foo(float x) {
    return f(g(x));
}

(a hosszú forma köztes termékekkel is működne.) Példa a Forth-ban :

  : foo g f ;

Az olyan nyelvekben, mint a C , az új függvény létrehozásának egyetlen módja az, ha definiálja azt a programforrásban, ami azt jelenti, hogy a függvények nem állíthatók össze futás közben . Előre definiált függvények tetszőleges összetételének értékelése azonban lehetséges:

#include <stdio.h>

typedef int FXN(int);

int f(int x) { return x+1; }
int g(int x) { return x*2; }
int h(int x) { return x-3; }

int eval(FXN *fs[], int size, int x)
{
   for (int i=0; i<size; i++) x = (*fs[i])(x);

   return x;
}

int main()
{
   // ((6+1)*2)-3 = 11
   FXN *arr[] = {f,g,h};
   printf("%d\n", eval(arr, 3, 6));

   // ((6-3)*2)+1 = 7
   arr[2] = f;  arr[0] = h;
   printf("%d\n", eval(arr, 3, 6));
}

Első osztályú kompozíció

A funkcionális programozási nyelvekben a függvényösszetétel természetesen magasabb rendű függvényként vagy operátorként is kifejezhető . Más programozási nyelveken megírhatja saját mechanizmusait a funkciók összetételének végrehajtására.

Haskell

A Haskell , A fent megadott példában pl válik:

foo = f . g

a beépített kompozíciós operátor (.) használatával, amely g- ként f- ként vagy f-vel komponált g- ként olvasható .

Maga a kompozíció operátora meghatározható a Haskellben egy lambda kifejezés segítségével :

(.) :: (b -> c) -> (a -> b) -> a -> c
f . g = \x -> f (g x)

Az első sorok a (.) Típusát írják le - pár függvényt vesz fel, és egy függvényt ad vissza. Ne feledje, hogy Haskell nem követeli meg az f és g pontos bemeneti és kimeneti típusainak megadását, csak a közöttük lévő kapcsolatokra (f-nek el kell fogadnia azt, amit g visszaad). Ez (.) Polimorf operátorrá teszi.

Selypít

A Lisp változatai , különösen a Scheme , a kód és az adatok felcserélhetősége, valamint a függvények kezelése rendkívül jól alkalmazható a variadikus kompozíciós operátor rekurzív meghatározásához .

(define (compose . fs)
  (if (null? fs) (lambda (x) x) ; if no argument is given, evaluates to the identity function
      (lambda (x) ((car fs) ((apply compose (cdr fs)) x)))))

; examples
(define (add-a-bang str)
  (string-append str "!"))

(define givebang
  (compose string->symbol add-a-bang symbol->string))

(givebang 'set) ; ===> set!

; anonymous composition
((compose sqrt negate square) 5) ; ===> 0+5i

APL

Az APL számos dialektusa a funkciókompozíciót a szimbólum segítségével építette fel . Ez a magasabb rendű funkció kiterjeszti a függvény összetételét a bal oldali függvény diadikus alkalmazására, A f∘g B azaz A f g B .

foofg

Ezenkívül meghatározhatja a függvény összetételét:

o{⍺⍺ ⍵⍵ }

Olyan nyelvjárásban, amely nem támogatja a zárójelek használatával történő inline meghatározást, a hagyományos meghatározás érhető el:

 r(f o g)x
  rf g x

Raku

A Hakuellhez hasonló Rakunak van egy beépített függvény-összetevő operátora, a fő különbség az, hogy vagy vagy o .

my &foo = &f  &g;

A Haskellhez hasonlóan maga is meghatározhatja az operátort. Valójában a következő a Raku kód, amelyet a Rakudo implementációban definiálnak .

# the implementation has a slightly different line here because it cheats
proto sub infix:<∘> (&?, &?) is equiv(&[~]) is assoc<left> {*}

multi sub infix:<∘> () { *.self } # allows `[∘] @array` to work when `@array` is empty
multi sub infix:<∘> (&f) { &f }   # allows `[∘] @array` to work when `@array` has one element
multi sub infix:<∘> (&f, &g --> Block) {
    (&f).count > 1
    ?? -> |args { f |g |args }
    !! -> |args { f g |args }
}

# alias it to the "Texas" spelling ( everything is bigger, and ASCII in Texas )
my &infix:<o> := &infix:<∘>;

Piton

A Python , olyan módon, hogy meghatározzák a készítmény bármely csoport a funkciók, használja csökkentése funkciót (használatra functools.reduce Pythonban 3):

# Available since Python v2.6
from functools import reduce

def compose(*funcs) -> int:
    """Compose a group of functions (f(g(h(...)))) into a single composite func."""
    return reduce(lambda f, g: lambda x: f(g(x)), funcs)

# Example
f = lambda x: x + 1
g = lambda x: x * 2
h = lambda x: x - 3

# Call the function x=10 : ((x-3)*2)+1 = 15
print(compose(f, g, h)(10))

JavaScript

A JavaScript- ben olyan funkcióként definiálhatjuk, amely két f és g függvényt vesz fel, és létrehoz egy függvényt:

function o(f, g) {
    return function(x) {
        return f(g(x));
    }
}

// Alternatively, using the rest operator and lambda expressions in ES2015
const compose = (...fs) => (x) => fs.reduceRight((acc, f) => f(acc), x)

C #

A C # -ben Func-ként definiálhatjuk, amely két Funcs f és g felvételével Func-ot hoz létre:

// Call example:
//   var c = Compose(f, g);
//
//   Func<int, bool> g = _ =>  ...
//   Func<bool, string> f = _ => ...

Func<TIn, TOut> Compose<TIn, TMid, TOut>(Func<TMid, TOut> f, Func<TIn, TMid> g) => _ => f(g(_));

Rubin

Az olyan nyelvek, mint a Ruby, lehetővé teszik egy bináris operátor létrehozását:

class Proc
  def compose(other_fn)
    ->(*as) { other_fn.call(call(*as)) }
  end
  alias_method :+, :compose
end

f = ->(x) { x * 2 }
g = ->(x) { x ** 3 }
(f + g).call(12) # => 13824

Azonban egy natív függvény-összetevő operátort vezettek be a Ruby 2.6-ban:

f = proc{|x| x + 2}
g = proc{|x| x * 3}
(f << g).call(3) # -> 11; identical to f(g(3))
(f >> g).call(3) # -> 15; identical to g(f(3))

Kutatási felmérés

Fogalmai készítmény, beleértve a kompozicionalitás és alakíthatóságának , annyira mindenütt, hogy számos szál kutatás külön fejlődtek. Az alábbiakban bemutatunk egy mintát arról a kutatásról, amelyben a kompozíció fogalma központi szerepet játszik.

Nagyszabású kompozíció

Egész programok vagy rendszerek funkcióként kezelhetők, amelyek könnyen összeállíthatók, ha a bemenetek és kimenetek jól meghatározott csővezetékek, amelyek lehetővé teszik a szűrők egyszerű összeállítását, olyan sikeresek voltak, hogy az operációs rendszerek tervezési mintájává vált .

A mellékhatásokkal járó kötelező eljárások megsértik a referenciák átláthatóságát , ezért nem tisztán összeállíthatók. Ha azonban a kód futtatása előtti és utáni "világállapotot" tekintjük be- és kimenetnek, akkor tiszta funkciót kap. Az ilyen funkciók összetétele megfelel az eljárások egymás utáni futtatásának. A monádok formalizmusa ezt az ötletet alkalmazza a mellékhatások és az I / O beépítésére a funkcionális nyelvekbe.

Lásd még

Megjegyzések

Hivatkozások