Ibland måste man bara göra en bättre musfälla. Att skapa språk i Racket cHoosInG THe Rätt verktyg för ett enkelt jobb är enkelt: en skruvmejsel är oftast det bästa alternativet när du behöver byta batteri i en leksak, och grepp är det självklara valet att kontrollera ett ord i ett textdokument. För mer komplexa uppgifter är valet av verktyg sällan så enkelt – desto mer för en programmeringsuppgift, där programmerare har en oöverträffad förmåga att konstruera sina egna verktyg. Programmerare löser ofta programmeringsproblem genom att skapa nya verktygsprogram, till exempel skript som genererar källkod från tabeller med data. Eftersom programmerare ofta bygger uppgiftsspecifika verktyg är ett sätt att göra dem mer produktiva att ge dem bättre verktygstillverkningsverktyg. När verktygen tar formen av programgeneratorer, leder denna idé till bibliotek för att skapa språk som är direkt extensible. Programmerare kan till och med uppmuntras att tänka på ett problem i form av ett språk som bättre skulle stödja uppgiften. Detta tillvägagångssätt kallas ibland språkorienterad programmering. Racket är både ett programmeringsspråk och en ram för att bygga programmeringsspråk. Ett Racketprogram kan innehålla definitioner som utökar syntaxen för språket för användning senare i samma program, och språktillägg kan paketeras som moduler för användning i flera program. Racket stöder en smidig väg från relativt enkla språk förlängningar till helt nya språk, eftersom ett programmeringsverktyg, som alla andra delar av programvara, är sannolikt att starta enkelt och växa som krav på språket ökar. Som ett exempel uppgift, överväga genomförandet av ett text-äventyr spel (även känd som interaktiv fiktion), där en spelare skriver kommandon för att flytta runt i en virtuell värld och interagera med objekt: Du står i en äng. Det finns ett hus norrut. > Norr Du står framför ett hus. Det finns en dörr här. > öppen dörr Dörren är låst. > För att göra spelet intressant, en programmerare måste befolka den virtuella världen med platser och saker som har ett rikt beteende. De flesta alla programmeringsspråk skulle kunna genomföra denna virtuella värld, men att välja rätt språkkonstruktion (dvs. rätt verktyg) för att representera varje spelelement är ett avgörande steg i utvecklingsprocessen. Rätt konstruktioner tillåter kommandon, platser och saker att skapas lätt-undvika fel-bene pannplate kod för att ställa in världens tillstånd och anslutningar-samtidigt som ett programmeringsspråks fulla kraft att genomföra beteenden. I ett allmänt programmeringsspråk är det sannolikt att ingen inbyggd språkkonstruktion passar perfekt. Till exempel, platser och saker kan vara objekt, medan kommandon kan genomföras som metoder. Spelets spelare övar dock inte anropsmetoder utan skriver istället kommandon som måste tolkas och dynamiskt kartläggas till svar på platser och saker. På samma sätt, spara och ladda ett spel kräver att inspektera och återställa läget för platser och saker, vilket delvis är en fråga om objekt serialisering men också att ställa in variabler till omarshalled värden (eller annars med hjälp av en indirektion genom en ordbok för varje referens från ett objekt till ett annat). Vissa programmeringsspråk inkluderar konstruktioner-såsom överbelastning eller lathet-som en smart programmerare kan utnyttja för att koda ett domän-specifikt språk. Rackets utformning behandlar problemet mer direkt; den ger programmerare verktyg för att explicit utöka programmeringsspråket med ny syntax. Vissa uppgifter kräver bara en liten utvidgning till kärnspråket, medan andra drar nytta av skapandet av ett helt nytt språk. Racket stöder båda ändarna av spektrumet, och det gör det på ett sätt som möjliggör en jämn progression från ena änden till den andra. Eftersom en programmerares behov eller ambitioner växer för en viss uppgift, kan programmeraren dra nytta av allt mer av Rackets enhetliga ramverk för språkutbyggnad och konstruktion. Den text-äventyr exempel presenteras här illustrerar utvecklingen från en enkel inbäddning i Racket till ett separat domän-specifikt språk (inklusive IDE stöd för syntax färgning), förklara relevanta Racket detaljer längs vägen; ingen tidigare kunskap om Racket är nödvändig. Läsare som föredrar en mer fullständig introduktion till språket bör konsultera The Racket Guide. Exemplet är en "leksak" i flera bemärkelser av världen, men det är också en skalmodell av branschpraxis. De flesta videospelsutvecklare använder ett anpassat språk, inklusive Racket-baserat språk som används för att implementera innehåll för den Uncharted video-game serien. 2 Tydligen, när miljarder av underhållning dollar står på spel, valet av programmeringsspråk frågor-även till den grad att skapa nya, specialspråk. Vår text äventyrsspel innehåller en fast uppsättning av platser, såsom en äng, hus, eller öken, och en fast uppsättning saker, såsom en dörr, nyckel, eller blomma. Spelaren navigerar världen och interagerar med saker med hjälp av kommandon som tolkas som antingen ett eller två ord: ett enda verb (det vill säga ett intransitivt verb, eftersom det inte har ett målobjekt) såsom hjälp eller titta; eller ett verb följt av namnet på en sak (det vill säga ett transitivt verb följt av ett substantiv) såsom öppen dörr eller få nyckel. Navigationsord som norr eller in behandlas som verb. En användare kan spara spelet med hjälp av spara och ladda verb, som fungerar överallt och ber användaren om ett filnamn. För att genomföra ett text-äventyr spel i Racket, skulle du börja med att deklarera strukturtyper för var och en av de tre spelelement som visas i Figur 1. Racket är en dialekt av Lisp och en ättling av Scheme, så dess syntax använder parenteser och en liberal grammatik av identifierare (till exempel, transitiv? är en identifierare). En semikolon introducerar en ny linje-avslutad kommentar. Fyrkantsfästen är utbytbara mot parenteser men används av konventioner i vissa sammanhang, såsom att gruppera ett fältnamn med modifierare. Den #:mutable modifier förklarar ett fält som muterbara, eftersom fält är oföränderliga som standard. (definiera namn (make-hash)); symbol för att placera/sakta (definiera element (make-hash)); plats/sak till symbol (definiera (record-element)! Namn val) (hash-set!) (hash-set! namnnamn val) (hash-set! element val namn)) Följaktligen är det fullständiga genomförandet av ängen: Den första strukturformen i koden binder verbet att fungera med ett argument för varje fält och skapa ett verb instans. Till exempel kan du definiera ett söder verb med alias s som (definiera söder (verb (lista'south's) "go south' #false)) Lisp och Racket program tenderar att använda strängar för texten som ska visas för en slutanvändare-till exempel verbbeskrivningar som "go south". En symbol, skriven med en ledande enda citat (t.ex. "södra"), används mer typiskt för ett internt namn, såsom ett verb alias. Med tanke på definitionen av söder och en sak, blomma, kan du definiera en äng plats där den södra verb flyttar spelaren till en öken plats som illustreras i Figur 2. Listfunktionen skapar en lista, medan parpar två värden. Cons-funktionen parar vanligtvis ihop ett element med en lista för att bilda en ny lista, men här används cons för att para ihop ett verb med en funktion som implementerar verbets svar. Lambdaformen skapar en anonym funktion, som i detta fall förväntar sig noll argument. När ett verbs svarsfunktion skapar en plats, som t.ex. öken i exemplet, flyttas spelets exekveringsmotor spelaren till den returnerade platsen. Spelet motorns stöd för att spara och ladda spelet tillstånd, under tiden, kräver en kartläggning mellan platser och deras namn. (Platser kan implementeras som objekt som kan serialiseras, men att återställa ett spel kräver både deserialization och uppdatering Racket nivå variabler såsom äng.) Skivan-elementet! Funktionen implementerar kartläggningar mellan namn och platser som visas i figur 3. Saker och ting måste definieras och registreras på ungefär samma sätt som platser. Verbs måste samlas in i en lista som ska användas av spelets kommandotolk. Slutligen behöver parsingen och exekveringsmotorn en uppsättning verb som fungerar överallt, var och en med sin svarsfunktion. Alla dessa bitar utgör den intressanta delen av spelet genomförandet, medan tolkning och utförande motor är några dussin rader av statisk infrastruktur. Se den medföljande sidofältet för länkar till det kompletta spelet implementationen. Den kod som behövs för att konstruera den virtuella världen är särskilt ordagrant. Även om de data-representation val som diskuterats tidigare är typiska för ett Racket program, en Racket programmerare är osannolikt att skriva repetitiv kod som direkt definierar och registrerar platser, eftersom det innehåller så många pannplattor listor, conses, och lambdas. Istället skulle en Racket programmerare skriva och skulle lägga till en definieringsform till Racket med hjälp av ett mönsterbaserat makro. Den enklaste formen av en sådan makro använder define-syntax-regel som beskrivs som Figur 4. Formuläret omedelbart efter definiera-syntax-regel är ett mönster, och blanketten efter mönstret är en mall. En användning av ett makro som matchar dess mönster ersätts av makrots mall, modulosubstitutioner av mönstervariabler för deras matchningar. ID-, desc-, thng-, vrb- och expr-identifierarna i detta mönster är mönstervariabler. Observera att definieringsformen inte kan vara en funktion. Öknen uttryck efter söder är i allmänhet ett uttryck vars utvärdering måste fördröjas tills den södra kommandot är figur 4. define-place makro. figur 5. den generaliserade definiera-plats och definiera-ting makros. ellipser fungerar på det uppenbara sättet, och med denna generaliserade definierad plats, kan du sätta både en kaktus och en nyckel initialt i öknen och svara på riktning verb andra än norr genom att stanna i öknen: (define-place öken "Du är i en öken." Makro för en sak är lika okomplicerat: 52 Co MMuniCations av e aC M  på jA nUA RY 2 0 1 2  till VO L. 5 5  på nO. 1 övning införd. Mer påtagligt bör formuläret binda variabeln äng så att Racket uttryck för kommandon kan hänvisa till platsen direkt. Dessutom används variabelns källnamn (i motsats till dess värde) för att registrera platsen i tabellen över element. Definiera-place makro hittills matchar exakt en sak på en plats och exakt ett verb och svar uttryck. För att generalisera till ett antal saker, verb och uttryck definierar du Detta exempel fyra verb: sluta som ett intransitivt verb utan alias; norrut som ett intransitivt verb med alias n och en föredragen beskrivning "gå norrut", knacka som ett transitivt verb (som indikeras av understrecket) utan alias; och få som ett transitivt verb med alias greppa och ta och föredra beskrivning "ta". Slutligen samlas alla dessa verb in i en lista som är bunden till allverbs för användning av spelets kommandotolk. Genomförandet av define-verbs formulär kräver en mer allmän typ av mönster matchning för att stödja olika former av verb specifikationer och att matcha = och _ som bokstavliga. Ett genomförande av define-verbs kan skjuta upp arbetet med att hantera ett individuellt verb till en define-one-verb makro, som använder define-syntax och syntaxregler som visas i figur 6. Definiera-plats, definiera saker, och definiera-verb makron är exempel på syntaktisk abstraktion. De abstrakt över upprepade mönster av syntax, så att en programmerare kan undvika pannplatte kod och koncentrera sig på skapandet av intressanta verb, platser och saker. Den reviderade spelimplementationen, som har en kompakt och läsbar implementation av den virtuella världen, är tillgänglig online (se medföljande sidofältet för länkar). En Racket programmerare som är intresserad av att skriva ett enda text-äventyr spel skulle sannolikt sluta utvidga språket vid denna punkt. Om text-äventyrsmotorn ska kunna återanvändas för flera världar, är det dock troligt att en Raketprogrammerare tar ett steg bortom syntaktisk abstraktion till syntaktisk förlängning. Skillnaden mellan abstraktion och förlängning ligger delvis i betraktarens öga, men förlängningen antyder att funktioner som plats och recordement! kan hållas privat, medan define-place exporteras för användning i den världsdefinierande modulen med implementation-oberoende semantik. I den världsdefinierande modulen lägger makron till ellipser till mönstret som visas i figur 5. Verberna är något knepigare, eftersom du vill göra enkla verb särskilt kompakta att ange, och du behöver en typ av mönster för intransitiva verb och en annan för transitiva verb. Följande exempel illustrerar målsyntaxen: (define-verbs all-verbs [quit] figur 7. den maskinskrivna kompilerings-tidsstrukturen. (begin-for-syntax (struct typed (id ; en identifierare typ) ; en sträng #: egendom prop: procedur (lambda (själv stx) (typed-id själv)))) En identifierare är skriven som en symbol, men med en # prefix, så att (typed #'gen-desert "plats") figur 6. define-one-verb och definiera-varhelst makron. såsom define-place har samma status som inbyggda former såsom define och lambda. För att göra detta skift, kan du sätta definiera-verbs, definiera-plats, definiera-ting, och definiera-varsomhelst definitioner i sin egen modul, kallas world.rkt. Den #lang racket linje som startar varje modul visar att modulen genomförs i racket språket. I world.rkt, kräver dessutom import både syntaktiska förlängningar och funktioner som exporteras av txtadv.rkt modulen. Eftersom makrobindning är en del av Racket-språket, i motsats till att implementeras som en separat preprocessor, kan makrobindningar arbeta med modulimport och exportera samma som variabla bindningar. I synnerhet, definitionen av define-verbs makro kan se verb konstruktören funktion på grund av reglerna för lexical omfattning, medan kod i world.rkt modul kan inte komma åt verb direkt på grund av samma scoping regler. Eftersom en användning av define-verbs i world.rkt expanderar till en användning av verb, krävs betydande språkmaskiner för Racket att upprätthålla lexical omfattning i närvaro av makroexpansion, men resultatet är att syntaktisk förlängning är lätt för programmerare. Den modulära spelet implementationen är tillgänglig online (se medföljande sidofältet för länkar). Även om world.rkt modulen inte direkt kan komma åt konstruktör funktioner som verb, modulen har fortfarande tillgång till alla Racket språk och, via behov, någon annan modul export. Fler begränsningar på world.rkt kan vara lämpligt för att säkerställa att antaganden om txtadv.rkt är uppfyllda. För att utöva ytterligare kontroll, kan du konvertera txtadv.rkt från en modul som exporterar en språktillägg till en som exporterar ett språk. Sedan, istället börjar med #lang racket, world.rkt börjar med #lang s-exp "txtadv.rkt" För nu, S-exp indikerar att språket i world.rkt använder S-uttryck notation (dvs parenteser), medan txtadv.rkt definierar syntaktiska former. Senare, S-uttryck och syntaktisk-form specifikationer kombineras till ett enda namn, analogt med #lang racket. Tillsammans med föränderliga world.rkt, kan du ändra txtadv.rkt att exportera allt från racket: #lang racket (tillhandahåll define-verbs.... (all-from-out racket)).... Istället för (all-from-out racket), kan du använda (utom-out (allfrom-out racket) kräver) för att hålla inne kravformuläret från världen.
Racket REF gör det möjligt att implementera anpassade programmeringsspråk ovanpå Racket.
Creating languages in Racket
{'venue': 'CACM', 'journal': None, 'mag_field_of_study': ['Computer Science']}