Funktionsobjekt
I datorprogrammering är ett funktionsobjekt en konstruktion som gör att ett objekt kan anropas eller anropas som om det vore en vanlig funktion , vanligtvis med samma syntax (en funktionsparameter som också kan vara en funktion). Funktionsobjekt kallas ofta för funktorer .
Beskrivning
En typisk användning av ett funktionsobjekt är att skriva callback- funktioner. En återuppringning på procedurspråk , såsom C , kan utföras med hjälp av funktionspekare . Det kan dock vara svårt eller besvärligt att skicka ett tillstånd till eller ur återuppringningsfunktionen. Denna begränsning hämmar också mer dynamiskt beteende hos funktionen. Ett funktionsobjekt löser dessa problem eftersom funktionen egentligen är en fasad för ett helt objekt som bär sitt eget tillstånd.
Många moderna (och några äldre) språk, t.ex. C++ , Eiffel , Groovy , Lisp , Smalltalk , Perl , PHP , Python , Ruby , Scala , och många andra, stöder förstklassiga funktionsobjekt och kan till och med använda dem på ett betydande sätt. Funktionella programmeringsspråk stöder dessutom stängningar , dvs förstklassiga funktioner som kan "stänga över" variabler i sin omgivande miljö vid skapandet. Under sammanställningen omvandlar en transformation som kallas lambdalyftning stängningarna till funktionsobjekt.
I C och C++
Betrakta exemplet med en sorteringsrutin som använder en återuppringningsfunktion för att definiera en beställningsrelation mellan ett par artiklar. Följande C-program använder funktionspekare:
0 0
0
#include <stdlib.h> /* qsort() callback funktion, returnerar < 0 om a < b, > 0 om a > b, 0 om a == b */ int compareInts ( const void * a , const void * b ) { return ( * ( int * ) a - * ( int * ) b ); } ... // prototyp av qsort är // void qsort(void *bas, size_t nel, size_t width, int (*compar)(const void *, const void *)); ... int main ( void ) { int items [] = { 4 , 3 , 1 , 2 }; qsort ( objekt , sizeof ( objekt ) / sizeof ( objekt [ ]), sizeof ( objekt [ ]), compareInts ); återvända ; }
I C++ kan ett funktionsobjekt användas istället för en vanlig funktion genom att definiera en klass som överbelastar funktionsanropsoperatören genom att definiera en operator()-
medlemsfunktion . I C++ kan detta se ut så här:
0
// komparatorpredikat: returnerar sant om a < b, false annars struktur IntComparator { bool operator ()( const int & a , const int & b ) const { return a < b ; } }; int main () { std :: vektor < int > objekt { 4 , 3 , 1 , 2 }; std :: sort ( items . begin (), items . end (), IntComparator ()); återvända ; }
Lägg märke till att syntaxen för att tillhandahålla callback till funktionen std::sort()
är identisk, men ett objekt skickas istället för en funktionspekare. När den anropas exekveras återuppringningsfunktionen precis som alla andra medlemsfunktioner och har därför full åtkomst till objektets andra medlemmar (data eller funktioner). Naturligtvis är detta bara ett trivialt exempel. För att förstå vilken kraft en funktion ger mer än en vanlig funktion, överväg det vanliga fallet att sortera objekt efter ett visst fält. I följande exempel används en funktion för att sortera en enkel personaldatabas efter varje anställds ID-nummer.
0
struct CompareBy { const std :: sträng SORT_FIELD ; CompareBy ( const std :: string & sort_field = "name" ) : SORT_FIELD ( sort_field ) { /* validate sort_field */ } bool operator ()( const Employee & a , const Employee & b ) { if ( SORT_FIELD == "name " ) returnera en . namn < b . namn ; annars if ( SORT_FIELD == "ålder" ) returnerar en . ålder < b . ålder ; annars if ( SORT_FIELD == "idnum" ) returnerar en . idnum < b . idnum ; else /* throw undantag eller något */ } }; int main () { std :: vektor < Anställd > emps ; /* kod för att fylla databasen */ // Sortera databasen efter anställds ID-nummer std :: sort ( emps . begin (), emps . end (), CompareBy ( "idnum" )); återvända ; }
I C++11 ger lambda-uttrycket ett mer kortfattat sätt att göra samma sak.
0
int main () { std :: vektor < Anställd > emps ; /* kod för att fylla databasen */ const std :: string sort_field = "idnum" ; std :: sort ( emps . begin (), emps . end (), [ & sort_field ]( const Employee & a , const Employee & b ){ /* kod för att välja och jämföra fält */ }); återvända ; }
Det är möjligt att använda funktionsobjekt i andra situationer än som callback-funktioner. I detta fall används normalt inte den förkortade termen funktor om funktionsobjektet. Fortsätter exemplet,
IntComparator cpm ; bool resultat = cpm ( a , b );
Förutom klasstypsfunktorer är andra typer av funktionsobjekt också möjliga i C++. De kan dra nytta av C++s medlemspekare eller mallfaciliteter . Mallarnas uttrycksfullhet gör att vissa funktionella programmeringstekniker kan användas, som att definiera funktionsobjekt i termer av andra funktionsobjekt (som funktionssammansättning ) . Mycket av C++ Standard Template Library (STL) använder sig mycket av mallbaserade funktionsobjekt.
Upprätthålla tillstånd
En annan fördel med funktionsobjekt är deras förmåga att upprätthålla ett tillstånd som påverkar operator()
mellan anrop. Till exempel definierar följande kod en generator som räknar från 10 och uppåt och anropas 11 gånger.
#inkludera <algoritm> #inkludera <iostream> #inkludera <iterator> klass CountFrom { public : CountFrom ( int count ) : count_ ( count ) {} int operator ()() { return count_ ++ ; } privat : int count_ ; }; int main () { const int state ( 10 ); std :: generera_n ( std :: ostream_iterator < int > ( std :: cout , " \n " ), 11 , CountFrom ( state )); }
I C++14 eller senare kan exemplet ovan skrivas om som:
#inkludera <algoritm> #inkludera <iostream> #inkludera <iterator> int main () { std :: generera_n ( std :: ostream_iterator < int > ( std :: cout , " \n " ), 11 , [ count = 10 ]() mutable { return count ++ ; }); }
I C#
I C# deklareras funktionsobjekt via delegater . En delegat kan deklareras med en namngiven metod eller ett lambdauttryck . Här är ett exempel med en namngiven metod.
använder System ; använder System.Collections.Generic ; public class ComparisonClass1 { public static int CompareFunction ( int x , int y ) { return x - y ; } public static void Main () { var items = new List < int > { 4 , 3 , 1 , 2 }; Jämförelse < int > del = CompareFunction ; föremål . Sortera ( del ); } }
Här är ett exempel med ett lambda-uttryck.
använder System ; använder System.Collections.Generic ; public class ComparisonClass2 { public static void Main () { var items = new List < int > { 4 , 3 , 1 , 2 }; föremål . Sortera (( x , y ) => x - y ); } }
I D
D tillhandahåller flera sätt att deklarera funktionsobjekt: Lisp/Python-stil via stängningar eller C#-stil via delegates :
bool hitta ( T )( T [] höstack , bool delegat ( T ) nåltest ) { foreach ( halm ; höstack ) { if ( nål_test ( halm )) return true ; } returnera false ; } void main () { int [] höstack = [ 345 , 15 , 457 , 9 , 56 , 123 , 456 ]; int nål = 123 ; bool needleTest ( int n ) { return n == needle ; } hävda ( hitta ( höstack , & nålTest )); }
Skillnaden mellan en delegat och en stängning i D bestäms automatiskt och konservativt av kompilatorn. D stöder också funktionsliteraler, som tillåter en definition av lambda-stil:
void main () { int [] höstack = [ 345 , 15 , 457 , 9 , 56 , 123 , 456 ]; int nål = 123 ; hävda ( hitta ( höstack , ( int n ) { return n == nål ; })); }
För att tillåta kompilatorn att infoga koden (se ovan), kan funktionsobjekt också specificeras C++-stil via operatörens överbelastning :
bool hitta ( T , F )( T [] hösack , F nåltest ) { foreach ( halm ; höstack ) { if ( nål_test ( halm ) ) return true ; } returnera false ; } void main () { int [] höstack = [ 345 , 15 , 457 , 9 , 56 , 123 , 456 ]; int nål = 123 ; class NeedleTest { int needle ; this ( int n ) { nål = n ; } bool opCall ( int n ) { return n == nål ; } } hävda ( hitta ( höstack , nytt NålTest ( nål ))); }
I Eiffel
I Eiffels mjukvaruutvecklingsmetod och språk, ses operationer och objekt alltid som separata begrepp. Agentmekanismen underlättar dock modelleringen av operationer som körtidsobjekt. Agenter uppfyller applikationsområdet som tillskrivs funktionsobjekt, som att skickas som argument i proceduranrop eller specificeras som callback-rutiner. Utformningen av agentmekanismen i Eiffel försöker återspegla metodens och språkets objektorienterade karaktär. En agent är ett objekt som vanligtvis är en direkt instans av en av de två biblioteksklasserna, som modellerar de två typerna av rutiner i Eiffel: PROCEDURE
och FUNCTION
. Dessa två klasser härstammar från den mer abstrakta RUTINEN
.
Inom mjukvarutext tillåter språknyckelordet agenter
att konstrueras i en kompakt form. I följande exempel är målet att lägga till åtgärden att stega mätaren framåt till listan över åtgärder som ska utföras i händelse av att en knapp klickas.
min_knapp . select_actions . förlänga ( agent my_gauge . step_forward )
Rutinutvidgningen som refereras till i exemplet ovan är en egenskap hos en klass i ett bibliotek för
grafiskt användargränssnitt (GUI) för att tillhandahålla händelsestyrda programmeringsmöjligheter .
I andra biblioteksklasser ses agenter användas för olika ändamål. I ett bibliotek som stöder datastrukturer, till exempel, utför en klass som modellerar linjära strukturer universell kvantifiering med en funktion for_all
av typen BOOLEAN
som accepterar en agent, en instans av FUNCTION
, som ett argument. Så i följande exempel körs my_action endast om alla medlemmar av
my_list
innehåller tecknet '!':
my_list : LINKED_LIST [ STRING ] ... om min_lista . for_all ( agent { STRING }. har ( '!' )) sedan my_action slut ...
När agenter skapas kan argumenten till rutinerna de modellerar och till och med målobjektet som de tillämpas på antingen stängas eller lämnas öppna . Stängda argument och mål ges värden när agenten skapas. Tilldelningen av värden för öppna argument och mål skjuts upp till någon tid efter att agenten har skapats. Rutinen for_all
förväntar sig som argument en agent som representerar en funktion med ett öppet argument eller mål som överensstämmer med den faktiska generiska parametern för strukturen ( STRING
i det här exemplet.)
När målet för en agent lämnas öppet, ersätts klassnamnet på det förväntade målet, omgivet av klammerparenteser, för en objektreferens som visas i textagenten {STRING}.has ('!') i
exemplet ovan. När ett argument lämnas öppet, kodas frågetecknet ('?') som en platshållare för det öppna argumentet.
Möjligheten att stänga eller lämna öppna mål och argument är avsedd att förbättra agentmekanismens flexibilitet. Tänk på en klass som innehåller följande procedur för att skriva ut en sträng på standardutdata efter en ny rad:
print_on_new_line ( s : STRING ) -- Skriv ut `s' föregås av en ny rad skriv ut ( "%N" + s ) slut
Följande utdrag, som antas vara i samma klass, använder print_on_new_line
för att demonstrera blandningen av öppna argument och öppna mål i agenter som används som argument till samma rutin.
my_list : LINKED_LIST [ STRING ] ... min_lista . do_all ( agent print_on_new_line ( ? )) my_list . do_all ( agent { STRING }. to_lower ) my_list . do_all ( agent print_on_new_line ( ? )) ...
Det här exemplet använder proceduren do_all
för linjära strukturer, som exekverar rutinen som modelleras av en agent för varje objekt i strukturen.
Sekvensen av tre instruktioner skriver ut strängarna i my_list
, konverterar strängarna till gemener och skriver dem sedan ut igen.
Procedur do_all
itererar över strukturen och kör rutinen och ersätter antingen det öppna argumentet med det aktuella objektet (i fallet med agenter baserade på print_on_new_line
), eller det öppna målet (i fallet med agenten baserad på to_lower
).
Öppna och stängda argument och mål tillåter också användning av rutiner som kräver fler argument än vad som krävs genom att stänga alla utom det nödvändiga antalet argument:
min_lista . do_all ( agent my_multi_arg_procedur ( closed_arg_1 , ? , closed_arg_2 , closed_arg_3 )
Eiffel-agentmekanismen beskrivs i Eiffels ISO/ECMA-standarddokument .
I Java
Java har inga förstklassiga funktioner , så funktionsobjekt uttrycks vanligtvis av ett gränssnitt med en enda metod (oftast Callable-
gränssnittet), vanligtvis med implementeringen som en anonym inre klass , eller, med början i Java 8, en lambda .
Som ett exempel från Javas standardbibliotek, java.util.Collections.sort()
tar en List
och en funktion vars roll är att jämföra objekt i Listan. Utan förstklassiga funktioner är funktionen en del av Comparator-gränssnittet. Detta kan användas enligt följande.
List < String > list = Arrayer . asList ( "10" , "1" , "20" , "11" , "21" , "12" ); Comparator < String > numStringComparator = new Comparator < String > () { public int compare ( String str1 , String str2 ) { return Heltal . värdeAv ( str1 ). compareTo ( Integer . valueOf ( str2 )); } }; Samlingar . sort ( lista , numStringComparator );
I Java 8+ kan detta skrivas som:
List < String > list = Arrayer . asList ( "10" , "1" , "20" , "11" , "21" , "12" ); Comparator < String > numStringComparator = ( str1 , str2 ) -> Heltal . värdeAv ( str1 ). compareTo ( Integer . valueOf ( str2 )); Samlingar . sort ( lista , numStringComparator );
I JavaScript
I JavaScript är funktioner förstklassiga objekt. JavaScript stöder även stängningar.
Jämför följande med det efterföljande Python-exemplet.
function Accumulator ( start ) { var current = start ; return function ( x ) { return current += x ; }; }
Ett exempel på detta i användning:
var a = Ackumulator ( 4 ); var x = a ( 5 ); // x har värdet 9 x = a ( 2 ); // x har värdet 11 var b = Accumulator ( 42 ); x = b ( 7 ); // x har värdet 49 (ström = 49 i stängning b) x = a ( 7 ); // x har värdet 18 (ström = 18 i stängning a)
I Julia
I Julia associeras metoder med typer, så det är möjligt att göra vilket godtyckligt Julia-objekt som helst "anropsbart" genom att lägga till metoder till dess typ. (Sådana "anropsbara" objekt kallas ibland "funktioner".)
Ett exempel är denna ackumulatorföränderliga struktur (baserad på Paul Grahams studie om programmeringsspråkets syntax och klarhet):
julia> föränderlig struktur Accumulator n :: Int end julia> funktion ( acc :: Accumulator )( n2 ) acc . n += n2 end julia> a = Accumulator ( 4 ) Accumulator(4) julia> a ( 5 ) 9 julia> a ( 2 ) 11 julia> b = Accumulator ( 42 ) Accumulator(42) julia> b ( 7 ) 49
En sådan ackumulator kan också implementeras med stängning:
julia> function Accumulator ( n0 ) n = n0 function ( n2 ) n += n2 end end Accumulator (generisk funktion med 1 metod) julia> a = Accumulator ( 4 ) (::#1) (generisk funktion med 1 metod) julia > a ( 5 ) 9 julia> a ( 2 ) 11 julia> b = Ackumulator ( 42 ) (::#1) (generisk funktion med 1 metod) julia> b ( 7 ) 49
I Lisp och Scheme
I Lisp-familjespråk som Common Lisp , Scheme och andra är funktioner objekt, precis som strängar, vektorer, listor och siffror. En stängningskonstruerande operator skapar ett funktionsobjekt från en del av programmet: den del av koden som ges som argument till operatorn är en del av funktionen, och det är den lexikaliska miljön likaså: bindningarna av de lexikalt synliga variablerna fångas in och lagras i funktionsobjektet, som oftare kallas en stängning . De fångade bindningarna spelar rollen som medlemsvariabler , och koddelen av stängningen spelar rollen som den anonyma medlemsfunktionen , precis som operatorn () i C++.
Stängningskonstruktorn har syntaxen (lambda (parametrar ...) kod ...)
. Delen (parametrar ...)
tillåter att ett gränssnitt deklareras, så att funktionen tar de deklarerade parametrarna. Koden ...-
delen består av uttryck som utvärderas när funktorn anropas.
Många användningar av funktorer i språk som C++ är helt enkelt emuleringar av den saknade stängningskonstruktorn. Eftersom programmeraren inte direkt kan konstruera en stängning måste de definiera en klass som har alla nödvändiga tillståndsvariabler, och även en medlemsfunktion. Konstruera sedan en instans av den klassen istället, och se till att alla medlemsvariabler initieras genom dess konstruktor. Värdena härleds just från de lokala variabler som borde fångas direkt av en stängning.
Ett funktionsobjekt som använder klasssystemet, ingen användning av stängningar:
( defclass counter () (( värde :inarg :value :accessor value-of ))) ( defmethod functor-call (( c counter )) ( incf ( värde-av c ))) ( defun make-counter ( initialt värde ) ( make-instans 'counter :value initial-value )) ;;; använd räknaren: ( defvar *c* ( make-counter 10 )) ( functor-call *c* ) --> 11 ( functor-call *c* ) --> 12
Eftersom det inte finns något standardsätt att göra funcallable objekt i Lisp, fejkar vi det genom att definiera en generisk funktion som kallas FUNCTOR-CALL. Detta kan specialiseras för vilken klass som helst. Standardfunktionen FUNCALL är inte generisk; det tar bara funktionsobjekt.
Det är denna FUNCTOR-CALL generiska funktion som ger oss funktionsobjekt, som är en datorprogrammeringskonstruktion som gör att ett objekt kan anropas eller anropas som om det vore en vanlig funktion, vanligtvis med samma syntax. Vi har nästan samma syntax: FUNCTOR-CALL istället för FUNCALL. Vissa Lisps tillhandahåller funktionsbara objekt som en enkel förlängning. Att göra objekt anropsbara med samma syntax som funktioner är en ganska trivial sak. Att få en funktionsanropsoperatör att arbeta med olika typer av funktionssaker , oavsett om de är klassobjekt eller stängningar, är inte mer komplicerat än att göra en +-operator som fungerar med olika typer av tal, som heltal, reella eller komplexa tal.
Nu är en räknare implementerad med en stängning. Detta är mycket mer kortfattat och direkt. Argumentet INITIAL-VALUE för fabriksfunktionen MAKE-COUNTER fångas och används direkt. Det behöver inte kopieras till något hjälpklassobjekt genom en konstruktor. Det är disken. Ett hjälpobjekt skapas, men det händer bakom kulisserna .
( defun make-counter ( värde ) ( lambda () ( incf värde ))) ;;; använd räknaren ( defvar *c* ( make-counter 10 )) ( funcall *c* ) ; --> 11 ( funcall *c* ) ; --> 12
Scheme gör stängningar ännu enklare, och Scheme-kod tenderar att använda sådan högre ordningsprogrammering något mer idiomatiskt.
( definiera ( make-counter value ) ( lambda () ( set! värde ( + värde 1 )) värde )) ;;; använd räknaren ( definiera c ( fabrikaträknare 10 )) ( c ) ; --> 11 ( c ) ; --> 12
Mer än en stängning kan skapas i samma lexikala miljö. En vektor av stängningar, som var och en implementerar en specifik typ av operation, kan ganska troget emulera ett objekt som har en uppsättning virtuella operationer. Den typen av med singelutskick kan göras fullt ut med stängningar.
Det finns alltså en sorts tunnel som grävs från båda sidor om det ökända berget. Programmerare i OOP-språk upptäcker funktionsobjekt genom att begränsa objekt till att ha en huvudfunktion för att göra det objektets funktionella syfte, och till och med eliminera dess namn så att det ser ut som att objektet anropas! Även om programmerare som använder stängningar inte är förvånade över att ett objekt kallas som en funktion, upptäcker de att flera stängningar som delar samma miljö kan tillhandahålla en komplett uppsättning abstrakta operationer som en virtuell tabell för enstaka sändningar av typen OOP .
I Objective-C
I Objective-C kan ett funktionsobjekt skapas från klassen NInvocation .
Konstruktion av ett funktionsobjekt kräver en metodsignatur, målobjektet och målväljaren. Här är ett exempel för att skapa en anrop till det aktuella objektets myMethod
:
// Konstruera ett funktionsobjekt SEL sel = @selector ( myMethod ); NSInvocation * inv = [ NSInvocation invocationWithMethodSignature : [ self methodSignatureForSelector : sel ]]; [ inv setTarget : self ]; [ inv setSelector : sel ]; // Gör själva anropet [ inv anropa ];
En fördel med NSInvocation
är att målobjektet kan modifieras efter skapande. En enda NSInvocation
kan skapas och sedan anropas för vart och ett av valfritt antal mål, till exempel från ett observerbart objekt. En NSInvocation
kan skapas från endast ett protokoll, men det är inte okomplicerat. Se här .
I Perl
I Perl kan ett funktionsobjekt skapas antingen från en klasss konstruktor som returnerar en funktion stängd över objektets instansdata, välsignad i klassen:
paket Acc1 ; sub new { min $klass = skift ; min $arg = skift ; min $obj = sub { my $num = shift ; $arg += $num ; }; välsigna $obj , $klass ; } 1 ;
eller genom att överbelasta &{}
-operatorn så att objektet kan användas som en funktion:
paket Acc2 ; use overload '&{}' => sub { my $self = shift ; sub { my $num = shift ; $self -> { arg } += $num ; } }; sub new { min $klass = skift ; min $arg = skift ; min $obj = { arg => $arg }; välsigna $obj , $klass ; } 1 ;
I båda fallen kan funktionsobjektet användas antingen med hjälp av syntaxen $ref->(@arguments) :
använd Acc1 ; min $a = Acc1 -> new ( 42 ); skriv ut $a -> ( 10 ), "\n" ; # utskrifter 52 print $a -> ( 8 ), "\n" ; # utskrifter 60
eller med hjälp av coderef-avledningssyntaxen &$ref(@arguments) :
använd Acc2 ; min $a = Acc2 -> new ( 12 ); print & $a ( 10 ), "\n" ; # prints 22 print & $a ( 8 ), "\n" ; # utskrifter 30
I PHP
PHP 5.3+ har förstklassiga funktioner som kan användas t.ex. som parameter till funktionen usort():
$a = array ( 3 , 1 , 4 ); usort ( $a , function ( $x , $y ) { return $x - $y ; });
PHP 5.3+, stöder även lambda-funktioner och stängningar.
function Accumulator ( $start ) { $current = $start ; return funktion ( $x ) använd ( & $current ) { return $current += $x ; }; }
Ett exempel på detta i användning:
$a = Ackumulator ( 4 ); $x = $a ( 5 ); echo "x = $x <br/>" ; // x = 9 $x = $a ( 2 ); echo "x = $x <br/>" ; // x = 11
Det är också möjligt i PHP 5.3+ att göra objekt anropsbara genom att lägga till en magisk __invoke()-metod till deras klass:
klass Minus { offentlig funktion __invoke ( $x , $y ) { return $x - $y ; } } $a = array ( 3 , 1 , 4 ); usort ( $a , nytt Minus ());
I PowerShell
I Windows PowerShell- språket är ett skriptblock en samling satser eller uttryck som kan användas som en enda enhet. Ett skriptblock kan acceptera argument och returnera värden. Ett skriptblock är en instans av ett Microsoft .NET Framework -typ System.Management.Automation.ScriptBlock.
Funktion Get-Accumulator ( $x ) { { param ( $y ) return $x += $y }. GetNewClosure () }
PS C:\> $a = Get-Accumulator 4 PS C:\> & $a 5 9 PS C:\> & $a 2 11 PS C:\> $b = Get-Accumulator 32 PS C:\> & $b 10 42
I Python
I Python är funktioner förstklassiga objekt, precis som strängar, siffror, listor etc. Denna funktion eliminerar behovet av att skriva ett funktionsobjekt i många fall. Alla objekt med en __call__()
-metod kan anropas med syntax för funktionsanrop.
Ett exempel är denna ackumulatorklass (baserad på Paul Grahams studie om programmeringsspråkssyntax och klarhet):
class Accumulator : def __init__ ( self , n ) -> None : self . n = n def __call__ ( själv , x ): själv . n += x returnerar själv . n
Ett exempel på detta som används (med den interaktiva tolken):
>>> a = Ackumulator ( 4 ) >>> a ( 5 ) 9 >>> a ( 2 ) 11 >>> b = Ackumulator ( 42 ) >>> b ( 7 ) 49
Eftersom funktioner är objekt kan de också definieras lokalt, givna attribut och returneras av andra funktioner, som visas i följande exempel:
def Ackumulator ( n ): def ink ( x ): icke-lokal n n += x retur n retur ink
I Ruby
I Ruby kan flera objekt betraktas som funktionsobjekt, i synnerhet Method- och Proc-objekt. Ruby har också två sorters objekt som kan ses som semifunktionsobjekt: UnboundMethod och block. UnboundMethods måste först bindas till ett objekt (och därmed bli en metod) innan de kan användas som ett funktionsobjekt. Block kan kallas som funktionsobjekt, men för att kunna användas i någon annan egenskap som ett objekt (t.ex. skickas som ett argument) måste de först konverteras till en Proc. På senare tid kan symboler (som nås via den bokstavliga unära indikatorn : )
också konverteras till Proc
s. Genom att använda Rubys unary &
operator – motsvarande att anropa to_proc
på ett objekt, och förutsatt att den metoden existerar – skapade Ruby Extensions-projektet ett enkelt hack.
klass Symbol def to_proc proc { | obj , * args | obj . skicka ( själv , * args ) } slut slut
Nu kan metoden foo
vara ett funktionsobjekt, dvs en Proc
, via &:foo
och användas via take_a_functor(&:foo)
. Symbol.to_proc
lades officiellt till Ruby den 11 juni 2006 under RubyKaigi2006. [1]
På grund av de olika formerna används inte termen Functor i Ruby för att betyda ett funktionsobjekt. Bara en typ av utsändningsdelegation som introducerats av Ruby Facets -projektet heter Functor. Den mest grundläggande definitionen är:
class Functor def initialize ( & func ) @func = func slut def method_missing ( op , * args , & blk ) @func . call ( op , * args , & blk ) end end
Denna användning är mer lik den som används av funktionella programmeringsspråk, som ML , och den ursprungliga matematiska terminologin.
Andra betydelser
I ett mer teoretiskt sammanhang kan ett funktionsobjekt anses vara vilken instans som helst av klassen av funktioner, särskilt i språk som Common Lisp där funktioner är förstklassiga objekt .
ML -familjen av funktionella programmeringsspråk använder termen funktor för att representera en mappning från moduler till moduler, eller från typer till typer och är en teknik för att återanvända kod. Funktioner som används på detta sätt är analoga med den ursprungliga matematiska betydelsen av functor i kategoriteorin , eller till användningen av generisk programmering i C++, Java eller Ada .
I Haskell används termen funktor även för ett begrepp relaterat till betydelsen av funktor i kategoriteorin.
I Prolog och relaterade språk är functor en synonym för funktionssymbol .
Se även
- Callback (datavetenskap)
- Stängning (datavetenskap)
- Funktionspekare
- Funktion av högre ordning
- Kommandomönster
- Curry
Anteckningar
Vidare läsning
- David Vandevoorde & Nicolai M Josuttis (2006). C++-mallar: The Complete Guide , ISBN 0-201-73484-2 : Specifikt är kapitel 22 ägnat åt funktionsobjekt.
externa länkar
- Beskrivning från Portland Pattern Repository
- C++ Advanced Design Issues - Asynkron C++ av Kevlin Henney
- The Function Pointer Tutorials av Lars Haendel (2000/2001)
- Artikel " Generalized Function Pointers " av Herb Sutter
- Generiska algoritmer för Java
- PHP-funktioner - Funktionsobjekt i PHP
- Vad i helvete är en functionoid, och varför skulle jag använda en? (C++ FAQ)