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

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