Ensiluokkainen toiminto - First-class function
In computer science , joka on ohjelmointikieli on sanottu ensimmäisen luokan toimintoja , jos se kohtelee toiminnot kuin ensimmäisen luokan kansalaisia . Tämä tarkoittaa sitä, että kieli tukee toimintojen siirtämistä argumentteina muille funktioille, niiden palauttamista muiden toimintojen arvoina ja niiden määrittämistä muuttujille tai tallennusta tietorakenteisiin. Jotkut ohjelmointikielen teoreetikot tarvitsevat tukea myös nimettömille toiminnoille (funktion literaaleille). Kielillä, joilla on ensiluokkaisia toimintoja, toimintojen nimillä ei ole erityisasemaa; niitä käsitellään kuin tavallisia muuttujia, joilla on funktiotyyppi . Termin loi Christopher Strachey "ensiluokkaisina kansalaisina" 1960-luvun puolivälissä.
Ensiluokkaiset toiminnot ovat välttämättömiä toiminnalliselle ohjelmointityylille , jossa korkeamman tason toimintojen käyttö on vakiokäytäntö. Yksinkertainen esimerkki korkeamman tilattu toiminto on kartta funktio, joka ottaa, kuten sen argumentteja, funktio ja luettelo, ja palauttaa listan muodostettu levittämällä toiminto jokaisen jäsenen luettelon. Jotta kieli tukee karttaa , sen on tuettava funktion välittämistä argumenttina.
On tiettyjä täytäntöönpanon vaikeuksia ohimennen toimii väitteitä tai palauttamalla ne tulokset, erityisesti kun läsnä on ei-paikallisia muuttujia käyttöön sisäkkäisiä ja anonyymi toimintoja . Historiallisesti näitä kutsuttiin funarg -ongelmiksi , joiden nimi tuli funktion argumentista. Varhaisilla pakottavilla kielillä nämä ongelmat vältettiin joko tukematta funktioita tulostyypeinä (esim. ALGOL 60 , Pascal ) tai jättämällä pois sisäkkäiset funktiot ja siten ei-paikalliset muuttujat (esim. C ). Varhainen funktionaalinen kieli Lisp käytti dynaamisen laajuuden lähestymistapaa , jossa ei-paikalliset muuttujat viittaavat kyseisen muuttujan lähimpään määritelmään funktion suorituskohdassa sen sijaan, että se määriteltiin. Asianmukainen tuki leksikkäille ensiluokkaisille toiminnoille otettiin käyttöön kaaviossa, ja se edellyttää toimintojen viittausten käsittelyä sulkijoina paljaiden toimintojen osoittimien sijasta , mikä tekee roskien keräämisestä välttämättömän.
Käsitteet
Tässä osassa verrataan sitä, miten tietyt ohjelmointi-idiomit käsitellään funktionaalisella kielellä ensiluokkaisilla toiminnoilla ( Haskell ) verrattuna pakottavaan kieleen, jossa toiminnot ovat toisen luokan kansalaisia ( C ).
Korkeamman tason funktiot: funktioiden välittäminen argumentteina
Kielellä, jossa funktiot ovat ensimmäisen luokan kansalaisia, funktiot voidaan välittää argumentteina muille funktioille samalla tavalla kuin muut arvot (funktiota, joka ottaa toisen funktion argumentiksi, kutsutaan ylemmän tason funktioksi). Haskellin kielellä :
map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs
Kielet, joilla toiminnot eivät ole ensiluokkaisia, antavat usein mahdollisuuden kirjoittaa korkeamman tason funktioita käyttämällä toimintoja, kuten funktio-osoittimia tai edustajia . Kielellä C :
void map(int (*f)(int), int x[], size_t n) {
for (int i = 0; i < n; i++)
x[i] = f(x[i]);
}
Näiden kahden lähestymistavan välillä on useita eroja, jotka eivät liity suoraan ensimmäisen luokan toimintojen tukemiseen. Haskell -näyte toimii luetteloilla ja C -näyte matriiseilla . Molemmat ovat luonnollisimpia yhdistettyjä tietorakenteita kullakin kielellä, ja C -otoksen käyttäminen linkitetyillä luetteloilla olisi tehnyt siitä tarpeettoman monimutkaisen. Tämä selittää myös sen, että C-toiminto tarvitsee lisäparametrin (joka antaa taulukon koon.) C-toiminto päivittää taulukon paikallaan palauttamatta arvoa, kun taas Haskellin tietorakenteet ovat pysyviä (uusi luettelo palautetaan kun vanha jätetään koskemattomaksi.) Haskell -näyte käyttää rekursiota listan läpikäymiseen , kun taas C -näyte käyttää iteraatiota . Jälleen kerran, tämä on luonnollisin tapa ilmaista tämä toiminto molemmilla kielillä, mutta Haskell -näyte olisi voitu ilmaista helposti kertaiseksi ja C -näyte rekursioksi. Lopuksi Haskell -funktiolla on polymorfinen tyyppi, koska C ei tue tätä, olemme kiinnittäneet kaikki tyyppimuuttujat tyyppivakioon int.
Anonyymit ja sisäkkäiset toiminnot
Kielillä, jotka tukevat nimettömiä toimintoja, voimme välittää tällaisen funktion argumenttina korkeamman tason funktiolle:
main = map (\x -> 3 * x + 1) [1, 2, 3, 4, 5]
Kielellä, joka ei tue nimettömiä toimintoja, meidän on sitottava se nimen sijaan:
int f(int x) {
return 3 * x + 1;
}
int main() {
int list[] = {1, 2, 3, 4, 5};
map(f, list, 5);
}
Ei-paikalliset muuttujat ja sulkemiset
Kun meillä on nimettömiä tai sisäkkäisiä toimintoja, heidän on luonnollista viitata kehonsa ulkopuolella oleviin muuttujiin (joita kutsutaan ei-paikallisiksi muuttujiksi ):
main = let a = 3
b = 1
in map (\x -> a * x + b) [1, 2, 3, 4, 5]
Jos funktiot esitetään paljailla funktio -osoittimilla, emme voi enää tietää, miten funktion rungon ulkopuolella oleva arvo tulisi siirtää sille, ja siksi sulkeminen on rakennettava manuaalisesti. Siksi emme voi puhua "ensiluokkaisista" toiminnoista täällä.
typedef struct {
int (*f)(int, int, int);
int *a;
int *b;
} closure_t;
void map(closure_t *closure, int x[], size_t n) {
for (int i = 0; i < n; ++i)
x[i] = (*closure->f)(*closure->a, *closure->b, x[i]);
}
int f(int a, int b, int x) {
return a * x + b;
}
void main() {
int l[] = {1, 2, 3, 4, 5};
int a = 3;
int b = 1;
closure_t closure = {f, &a, &b};
map(&closure, l, 5);
}
Huomaa myös, että tämä mapon nyt erikoistunut toimintoihin, jotka viittaavat kahteen intympäristöönsä kuulumattomiin s. Tämä voidaan määrittää yleisemmin, mutta vaatii enemmän kattilalevykoodia . Jos fse olisi ollut sisäkkäinen toiminto, olisimme silti törmänneet samaan ongelmaan, ja siksi niitä ei tueta C.
Korkeamman tason toiminnot: funktioiden palauttaminen tuloksina
Kun palautat funktion, palaamme itse asiassa sen sulkemisen. C -esimerkissä kaikki sulkemisen siepatut paikalliset muuttujat menevät soveltamisalan ulkopuolelle, kun palaamme sulkemista rakentavasta funktiosta. Sulkemisen pakottaminen myöhemmässä vaiheessa johtaa määrittelemättömään käyttäytymiseen, mikä saattaa vioittaa pinon. Tämä tunnetaan ylöspäin suuntautuvana funarg -ongelmana .
Funktioiden määrittäminen muuttujille
Funktioiden määrittäminen muuttujille ja niiden tallentaminen (maailmanlaajuisiin) tietorakenteisiin kärsii mahdollisesti samoista vaikeuksista kuin funktioiden palauttaminen.
f :: [[Integer] -> [Integer]]
f = let a = 3
b = 1
in [map (\x -> a * x + b), map (\x -> b * x + a)]
Toimintojen yhtäläisyys
Koska useimpia kirjaimita ja arvoja voidaan testata tasa -arvon vuoksi, on luonnollista kysyä, voiko ohjelmointikieli tukea tasa -arvon testaustoimintoja. Jatkotarkastuksessa tämä kysymys näyttää vaikeammalta, ja on erotettava useat toiminnalliset tasa -arvotyypit:
- Laaja tasa -arvo
- Kaksi funktiota f ja g katsotaan laajasti yhtäläisiksi, jos ne sopivat kaikkien tulojensa ulostuloista (∀ x . F ( x ) = g ( x )). Tämän tasa -arvon määritelmän mukaan esimerkiksi mitä tahansa kahta vakaan lajittelualgoritmin toteutusta , kuten lisäyslajittelua ja yhdistämislajittelua , pidettäisiin yhtäläisinä. Laajennetusta tasa -arvosta päättäminen on päättämätöntä yleensä ja jopa toiminnoissa, joilla on rajalliset alueet, jotka ovat usein vaikeita. Tästä syystä mikään ohjelmointikieli ei toteuta toiminnallista tasa -arvoa laajuisena tasa -arvona.
- Intensiivinen tasa -arvo
- Intensionaalisen tasa -arvon mukaan kahta funktiota f ja g pidetään yhtäläisinä, jos niillä on sama "sisäinen rakenne". Tällainen tasa voitaisiin toteuttaa tulkita kielillä vertaamalla lähdekoodia funktion elinten (kuten tulkittuna Lisp 1.5) tai kohdekoodin on koottu kielillä . Intensionaalinen tasa -arvo merkitsee laajuista tasa -arvoa (olettaen, että toiminnot ovat deterministisiä ja niissä ei ole piilotettuja syötteitä, kuten ohjelmalaskuri tai muuttuva globaali muuttuja .)
- Viite -tasa -arvo
- Koska laajennetun ja intensiivisen tasa -arvon toteuttaminen on epäkäytännöllistä, useimmat tasa -arvon testaustoimintoja tukevat kielet käyttävät tasa -arvoa. Kaikille toiminnoille tai sulkemisille annetaan yksilöllinen tunniste (yleensä funktion elimen osoite tai sulkeminen) ja tasa -arvo päätetään tunnuksen yhtäläisyyden perusteella. Kaksi erikseen määriteltyä, mutta muuten identtistä funktion määritelmää pidetään eriarvoisina. Viittaava tasa -arvo merkitsee intensiivistä ja laajaa tasa -arvoa. Viitteellinen tasa -arvo rikkoo viittausten läpinäkyvyyttä, eikä sitä siksi tueta puhtailla kielillä, kuten Haskell.
Tyyppiteoria
In type Teoriassa , tarkoitetuista toiminnoista hyväksyä arvoja tyypin ja palauttamalla arvot tyypin B voidaan kirjoittaa → B tai B . Vuonna Curry-Howard vastaavuus , toiminto tyypit liittyvät looginen seuraus ; lambda -abstraktio vastaa hypoteettisten oletusten purkamista ja funktion sovellus modus ponens -päätössääntöä . Ohjelmointitoimintojen tavanomaisen tapauksen lisäksi tyyppiteoria käyttää myös ensiluokkaisia funktioita assosiatiivisten matriisien ja vastaavien tietorakenteiden mallintamiseen .
In luokka-teoreettinen tilit ohjelmointi, saatavuus ensimmäisen luokan toimintojen vastaa suljettuun ryhmään oletus. Esimerkiksi yksinkertaisesti kirjoitettu lambda -laskenta vastaa Cartesian suljettujen luokkien sisäistä kieltä .
Kielten tuki
Toiminnallisilla ohjelmointikielillä, kuten Scheme , ML , Haskell , F# ja Scala , on kaikilla ensiluokkaisia toimintoja. Kun Lisp , yksi varhaisimmista funktionaalisista kielistä, suunniteltiin, kaikki ensimmäisen luokan toimintojen osa-alueet eivät sitten olleet oikein ymmärrettyjä, mikä johti toimintojen dynaamisuuteen. Myöhemmissä Scheme- ja Common Lisp -murteissa on leksikaalisesti määriteltyjä ensiluokkaisia toimintoja.
Monilla skriptikielillä, kuten Perl , Python , PHP , Lua , Tcl /Tk, JavaScript ja Io , on ensiluokkaisia toimintoja.
Pakollisten kielten osalta on tehtävä ero Algolin ja sen jälkeläisten, kuten Pascalin, perinteisen C-perheen ja nykyaikaisten roskakoristeiden välillä. Algol-perhe on sallinut sisäkkäiset funktiot ja ylemmän asteen ottofunktion argumentteina, mutta ei korkeamman asteen toimintoja, jotka palauttavat funktioita tuloksina (paitsi Algol 68, joka sallii tämän). Syynä tähän oli se, että ei tiedetty, miten käsitellä muita kuin paikallisia muuttujia, jos sisäinen funktio palautettiin tämän seurauksena (ja Algol 68 tuottaa tällaisissa tapauksissa ajonaikaisia virheitä).
C -perhe salli molempien toimintojen siirtämisen argumentteina ja niiden palauttamisen tuloksina, mutta välttyi ongelmilta tukematta sisäkkäisiä toimintoja. (Gcc-kääntäjä sallii ne laajennuksena.) Koska funktioiden palauttamisen hyödyllisyys perustuu ensisijaisesti kykyyn palauttaa sisäkkäisiä funktioita, jotka ovat tallentaneet ei-paikallisia muuttujia, ylimmän tason toimintojen sijaan näillä kielillä ei yleensä katsota olevan ensimmäistä -luokan toiminnot.
Nykyaikaiset pakottavat kielet tukevat usein roskien keräystä, mikä tekee ensimmäisen luokan toimintojen toteuttamisesta mahdollista. Ensiluokkaisia toimintoja on usein tuettu vain kielen myöhemmissä versioissa, mukaan lukien C# 2.0 ja Applen Blocks-laajennus C, C ++ ja Objective-C. C ++ 11 on lisännyt tuen nimettömille toiminnoille ja kielen sulkemisille, mutta koska kieltä ei kerätä roskakoriin, on kiinnitettävä erityistä huomiota siihen, että funktioiden ei-paikalliset muuttujat palautetaan tuloksina (katso alla ).
| Kieli | Korkeamman tason toiminnot | Sisäkkäiset toiminnot | Ei-paikalliset muuttujat | Huomautuksia | ||||
|---|---|---|---|---|---|---|---|---|
| Argumentit | Tulokset | Nimetty | Anonyymi | Sulkeminen | Osittainen sovellus | |||
| Algolin perhe | ALGOL 60 | Joo | Ei | Joo | Ei | Alaspäin | Ei | Onko toiminnotyyppejä . |
| ALGOL 68 | Joo | Joo | Joo | Joo | Alaspäin | Ei | ||
| Pascal | Joo | Ei | Joo | Ei | Alaspäin | Ei | ||
| Ada | Joo | Ei | Joo | Ei | Alaspäin | Ei | ||
| Oberon | Joo | Vain sisäkkäin | Joo | Ei | Alaspäin | Ei | ||
| Delfoi | Joo | Joo | Joo | 2009 | 2009 | Ei | ||
| C perhe | C | Joo | Joo | Kyllä GNU C: ssä | Ei | Ei | Ei | Sisältää toiminto -osoittimia . |
| C ++ | Joo | Joo | C ++ 11 | C ++ 11 | C ++ 11 | C ++ 11 | Sisältää toiminto -osoittimia, toiminto -objekteja . (Katso myös alla.)
Selkeä osittainen käyttö mahdollista |
|
| C# | Joo | Joo | 7 | 2.0 / 3.0 | 2.0 | 3.0 | Sisältää edustajia (2.0) ja lambda -lausekkeita (3.0). | |
| Tavoite-C | Joo | Joo | Käyttämällä nimettömiä | 2.0 + lohkot | 2.0 + lohkot | Ei | Sisältää toiminto -osoittimia. | |
| Java | Joo | Joo | Käyttämällä nimettömiä | Java 8 | Java 8 | Ei | Sisältää nimettömiä sisäluokkia . | |
| Mennä | Joo | Joo | Käyttämällä nimettömiä | Joo | Joo | Joo | ||
| Limbo | Joo | Joo | Joo | Joo | Joo | Ei | ||
| Newsqueak | Joo | Joo | Joo | Joo | Joo | Ei | ||
| Ruoste | Joo | Joo | Joo | Joo | Joo | Joo | ||
| Toiminnalliset kielet | Lisp | Syntaksi | Syntaksi | Joo | Joo | Yhteinen Lisp | Ei | (Katso alempaa) |
| Kaavio | Joo | Joo | Joo | Joo | Joo | SRFI 26 | ||
| Julia | Joo | Joo | Joo | Joo | Joo | Joo | ||
| Clojure | Joo | Joo | Joo | Joo | Joo | Joo | ||
| ML | Joo | Joo | Joo | Joo | Joo | Joo | ||
| Haskell | Joo | Joo | Joo | Joo | Joo | Joo | ||
| Scala | Joo | Joo | Joo | Joo | Joo | Joo | ||
| F# | Joo | Joo | Joo | Joo | Joo | Joo | ||
| OCaml | Joo | Joo | Joo | Joo | Joo | Joo | ||
| Skriptikielet | Io | Joo | Joo | Joo | Joo | Joo | Ei | |
| JavaScript | Joo | Joo | Joo | Joo | Joo | ECMAScript 5 | Osittainen sovellus mahdollinen käyttäjän maakoodilla ES3: ssa | |
| Lua | Joo | Joo | Joo | Joo | Joo | Joo | ||
| PHP | Joo | Joo | Käyttämällä nimettömiä | 5.3 | 5.3 | Ei | Osittainen sovellus mahdollinen käyttäjän maakoodilla. | |
| Perl | Joo | Joo | 6 | Joo | Joo | 6 | ||
| Python | Joo | Joo | Joo | Vain lausekkeet | Joo | 2.5 | (Katso alempaa) | |
| Rubiini | Syntaksi | Syntaksi | Tarkastamaton | Joo | Joo | 1.9 | (Katso alempaa) | |
| Muut kielet | Fortran | Joo | Joo | Joo | Ei | Ei | Ei | |
| Vaahtera | Joo | Joo | Joo | Joo | Joo | Ei | ||
| Mathematica | Joo | Joo | Joo | Joo | Joo | Ei | ||
| MATLAB | Joo | Joo | Joo | Joo | Joo | Joo | Osittainen sovellus mahdollista luomalla uusia toimintoja automaattisesti. | |
| Rupattelu | Joo | Joo | Joo | Joo | Joo | Osittainen | Osittainen hakemus mahdollista kirjaston kautta. | |
| Nopea | Joo | Joo | Joo | Joo | Joo | Joo | ||
- C ++
- C ++ 11- sulkimet voivat kaapata ei-paikallisia muuttujia kopiorakenteella, viitteellä (pidentämättä niiden käyttöikää) tai siirtorakenteella (muuttuja elää niin kauan kuin sulkeminen). Ensimmäinen vaihtoehto on turvallinen, jos sulkeminen palautetaan, mutta vaatii kopion, eikä sitä voida käyttää alkuperäisen muuttujan muokkaamiseen (jota ei ehkä ole enää olemassa sulkemisen kutsumishetkellä). Toinen vaihtoehto välttää mahdollisesti kalliin kopion ja mahdollistaa alkuperäisen muuttujan muokkaamisen, mutta se ei ole turvallinen, jos sulkeminen palautetaan (katso roikkuvat viitteet ). Kolmas vaihtoehto on turvallinen, jos sulkeminen palautetaan eikä vaadi kopiota, mutta sitä ei voida käyttää myöskään alkuperäisen muuttujan muokkaamiseen.
- Java
- Java 8: n sulkemiset voivat kaapata vain lopullisia tai "tehokkaasti lopullisia" ei-paikallisia muuttujia. Javan funktiotyypit esitetään luokina. Anonyymit toiminnot käyttävät kontekstista pääteltyä tyyppiä. Menetelmäviittauksia on rajoitetusti. Lisätietoja on kohdassa Anonyymi toiminto § Java -rajoitukset .
- Lisp
- Lexisesti laajennetut Lisp -versiot tukevat sulkemista. Dynaamisesti laajennetut vaihtoehdot eivät tue sulkimia tai tarvitsevat erikoisrakenteen sulkimien luomiseksi.
- In Common Lisp , tunniste funktion toiminto nimiavaruuden ei voida käyttää viittaus ensimmäisen luokan arvo. Erikoisoperaattoria
functionon käytettävä funktion noutamiseen arvona:(function foo)arvioi funktio -objektiksi.#'fooon olemassa lyhenteenä. Soveltaa tällaista toimintoa esine, yksi on käytettäväfuncalltoiminto:(funcall #'foo bar baz). - Python
- Selkeä osittainen sovellus
functools.partialversiosta 2.5 jaoperator.methodcallerversiosta 2.6 lähtien. - Rubiini
- Säännöllisen "toiminnon" tunnusta Rubyssa (joka on todella menetelmä) ei voida käyttää arvona tai välittää. Se on ensin haettu osaksi
MethodtaiProcesine, jota käytetään ensimmäisen luokan tiedot. Tällaisen funktio -objektin kutsumisen syntaksi eroaa tavallisista menetelmistä. - Sisäkkäisten menetelmien määritelmät eivät todellakaan sisällä soveltamisalaa.
- Selvää currya
[1].
Katso myös
- Defunktionalisointi
- eval
- Ensiluokkainen viesti
- Kappa calculus- formalismi, joka sulkee pois ensimmäisen luokan toiminnot
- Mies tai poika testi
- Osittainen sovellus
Huomautuksia
Viitteet
- Leonidas Fegaras . "Toiminnalliset kielet ja korkeamman tason toiminnot" . CSE5317/CSE4305: Kääntäjien suunnittelu ja rakentaminen. Texasin yliopisto Arlingtonissa.