Композиција функција

У програмирању, композиција функција представља акт или механизам за комбиновање више једноставних функција ради стварања сложеније функције. Уобичајено је да се резултат сваке фунцкије преноси као аргумент следеће, а резултат последње је резултат целине.

Програмери често примењују функције на резултате неких других функција што допуштају готово сви програмски језици. У неким случајевима композиција функција је занимљива сама по себи као функција.Таква функција се увек може дефинисати, али језици са првокласним функцијама то олакшавају.

Могућност једноставне композиције функција подстиче рефакторисање функција за одржавање и поновну употребу кода. Генерално, велики системи се могу правити композицијом читавих програма.

Једноставно речено, композиција функција се односи на функције које раде на ограниченој количини података, сваки корак их узастопно обрађује пре него што пошаље следећем. Функције које обрађују бесконачно података познате су као филтри и повезане су у цевовод (pipeline), што је аналогно композицији функција и може се истовремено извршити.

Позиви композиције функције

уреди

На пример, претпоставимо да имамо две функције f и g, у облику z=f(y) и y=g(x). Њихова композиција подразмева прво израчунавање y=g(x) , а затим коришћење тог y за израчунавање z=f(y) . Следи пример у програмском језику C :

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

Кораци се могу комбиновати ако не задамо име средњем резултату:

z = f(g(x));

Упркос разликама у дужини, ове две имплементације дају исти резултат. Друга имплементација садржи само једну линију кода и назива се "highly composed" образац. Читљивост, а самим тим одрживост је једна од предности високо сређених облика, јер захтевају мање линија кода, минимизирајући ,,површину програма". [1]DeMarco и Lister емпиријски потврђују обрнут однос између површине и одржања.[2] С друге стране, високо компоновани облици могу бити претерани. Коришћење превише функција може имати супротан ефекат, чинећи код мање одрживим. У језицима који користе стек композиција функције је још природннија : изводи се спајањем и обично је примарна метода код дизајнирања програма.Следи пример у програмском језику Forth :

g f

Ово ће узети све што је било на стеку раније, применити  g, затим f, и оставити резултат на стеку.

Именовање композиције функције

уреди

Сада претпоставимо да је комбинација позивања f() на резултат g() је често корисна и желимо је назвати foo() да би се могла користити као функција сама по себи .

У већини програмских језика, можемо дефинисати нову имплементирану преко композиције. Пример у C- у:

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

Пример у Forth :

  : foo g f ;

У програмском језику C једини начин да се креира нова функција је дефинисање исте у изворном коду, што значи да не могу бити додате док је програм покренут. Међутим, могућа је процена произвољне композиције унапред дефинисаних функција:

#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));
}

Првокласна композиција

уреди

У функционалним програмским језицима, композиција функција може се природно изразити као функција вишег реда или оператор. У другим програмским језицима може се написати сопствени механизам за извођење композиције функција.

У програмском језику Haskell , горенаведени пример постаје :

foo = f . g

коришћењем уграђеног оператора композиције (.), који се може читати као f након g или g састављен са f . Сам оператор композиције се може дефинисати у Haskell помођу ламбда израза (рачуна):

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

Haskell не захтева спецификацију конкретних типова улаза и излаза f и g, само односе између њих (f мора прихватити оно што g враћа). Овим (.) постаје полиморфни оператор.

Варијанте Lisp-а посебно Scheme , замењивост кода и података, заједно са третманом функција, дају се изузетно добро за рекурзивну дефиницију варијабилног композиционог оператора.

(define (compose . fs)
  (if (null? fs) (lambda (x) x) ; ако није дат аргумент процењује се као функција идентитета
      (lambda (x) ((car fs) ((apply compose (cdr fs)) x)))))

; примери
(define (add-a-bang str)
  (string-append str "!"))

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

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

;анонимна композиција
((compose sqrt negate square) 5) ; ===> 0+5i

Многи дијалекти APL-а имају уграђену функцијску композицију, која се користи као симбол ∘. Ова функција вишег реда проширује састав функције на дијадичну примену функције леве стране тако да A f∘g B је A f g B.

foofg

Поред тога може се дефинисати композиција функције:

o{⍺⍺ ⍵⍵ }

У дијалекту који не подржава инлине дефиницију помоћу заграде, доступна је традиционална дефиниција:

 r(f o g)x
  rf g x

Попут Haskell-а, Raku има уграђен оператор композиције функција, главна разлика је у томе што се представља као ∘ или о.

my &foo = &f ∘ &g;

У наставку је Raku код који се користи да се дефинише оператор у Rakudo имплементацији.

proto sub infix:<∘> (&?, &?) is equiv(&[~]) is assoc<left> {*}

multi sub infix:<∘> () { *.self } # омогућава да `[∘] @array` ради када је `@array`празан
multi sub infix:<∘> (&f) { &f }   # омогућава да  `[∘] @array`ради када `@array`има један елементt
multi sub infix:<∘> (&f, &g --> Block) {
    (&f).count > 1
    ?? -> |args { f |g |args }
    !! -> |args { f g |args }
}

my &infix:<o> := &infix:<∘>;

У Python-у се композиција функције дефинише коришћењем  reduce функције (functools.reduce у Python 3) :

# Доступно од верзије Python v2.6
from functools import reduce

def compose(*funcs) -> int:
    """Састављање групе композиција (f(g(h(...)))) у једну композитну функцију"""
    return reduce(lambda f, g: lambda x: f(g(x)), funcs)

# Примери
f = lambda x: x + 1
g = lambda x: x * 2
h = lambda x: x - 3

# Позивање функције x=10 : ((x-3)*2)+1 = 15
print(compose(f, g, h)(10))

JavaScript

уреди

У овом програмском језику може се дефинисати као функција која узима две функције f и g и тако прави нову функцију:

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

// Коришћење осталих оператора и ламбда израза у ES2015
const compose = (...fs) => (x) => fs.reduceRight((acc, f) => f(acc), x)

У програмском језику C# се може дефинисати помоћу Func :

// Пример позива:
//   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(_));

Језици попут језика Ruby-а омогућавају самостално прављење бинарног оператера:

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

Међутим, сопствени оператор композиције функције уведен је у Ruby 2.6 :[3]

f = proc{|x| x + 2}
g = proc{|x| x * 3}
(f << g).call(3) # -> 11; идентично  f(g(3))
(f >> g).call(3) # -> 15; идентично g(f(3))

Истраживачка анкета

уреди

Појмови композиције, укључујући принцип композиционости и компостибилности, су толико свеприсутни да су се бројне теме истраживања одвојено развијале.Следе узорковања врсте истраживања у којима је појам композиције централни.

  • Steele (1994) је директно применио функциону композицију на скуп ,,грађевинских блокова" који су у програмском језику Haskell познати као "монаде".
  • Meyer (1988) се бавио проблемом поновне употребе софтвера у смислу компостибилност.
  • Abadi & Lamport (1993) су формално дефинисали правило композиције функције која осигурава сигурност и живот програма.
  • Kracht (2001) је идентификовао појачани облик композиционости стављајући га у семиотички систем и примењујући га на проблем структуралне нејасноће који се често среће у рачунарској лингвистици.
  • van Gelder & Port (1993) испитала је улогу композиционости у аналогним аспектима обраде природног језика.

Композиција великог обима

уреди

Читави програми или системи могу се третирати као функције, које се лако могу саставити ако су њихови улази и излази добро дефинисани,[4] цевоводи омогућавају лако састављање филтера, па је све то постало дизајнерски образац оперативних система.

Императивни поступци са спољним ефектима крше референтну транспарентност и због тога се не могу чисто саставити. Међутим, ако узимате у обзир „стање света“ пре и после покретања кода као његове улазе и излазе, добићете чисту функцију. Састав таквих функција одговара покретању процедура једне за другом . Монадски формализам користи ову идеју да уврсти нуспојаве и И / О у функционалне језике.

Види још

уреди

Референце

уреди
  1. ^ Cox, Brad (1986). Object-oriented Programming, an Evolutionary Approach. стр. 15—17. ISBN 978-0-201-54834-1. 
  2. ^ DeMarco, Tom; Lister, Tim (1995). Why Does Software Cost So Much, and other puzzles of the Information Age. New York, NY: Dorset House. ISBN 0-932633-34-X. 
  3. ^ „Ruby 2.6.0 Released”. www.ruby-lang.org. Приступљено 2020-07-14. 
  4. ^ Raymond, Eric S. (2003). „1.6.3 Rule of Composition: Design programs to be connected with other programs”. The Art of Unix Programming. Addison-Wesley. стр. 15—16. ISBN 978-0-13-142901-7.