Fantom


lørdag 10. desember 2011 Julekalender Polyglot Actor model Fantom Samtidighet

For cirka seks år siden dukket det opp et interessant språk som utviklerne kalte Fan. Det viste seg derimot å være vanskelig å google det navnet, og spåket endret derfor navn til Fantom i 2009. Fantom er en typisk smeltedigel av egenskaper man finner i andre språk, og er ment å være et praktisk verktøy. Det er objektorientert, støtter funksjonell programmering, og har actors – eller støtte for samtidighetsbasert programmering som Joe Armstrong ville ha sagt.

Fantom er fortsatt under aktiv utvikling og endring, men er likevel robust og er brukt i produksjon flere steder.

fantom

Fantom-kode har en Java/C#-lignende syntaks som bør være grei å forholde seg til for de aller fleste. Språket er statisk typet, men har også flere dynamiske features som type inference, implisitt casting, og en dynamic invoke-operator som lar deg kalle en hvilken som helst metode uten statisk kontroll.

Det finnes to ulike typer i Fantom: klasser og mixins. Klasser danner grunnlaget for objekter, og all data i Fantom er objekter, inkludert value types som Int og Bool. Akkurat som Java og C# har Fantom single inheritance, men mixins gir deg mulighet til å utvide objektene på måter Java/C# ikke kan (for eksempel sånn som jeg presenterte i posten om DCI arkitekturen i mars 2010).

Kan kjøre på ulike plattformer

Fantom er laget for å være portabelt, og kan kjøres på Java VM, på .NET CLR, og med JavaScript i en browser. Fantom kompileres til Fantom bytecode, og denne kompileres igjen ned til valgt platform. Per i dag er det Java-runtimen som er mest stabil, og .NET-implementasjonen er mer et proof-of-concept.

Hvordan ser koden ut?

Du begynnes sikkert å bli litt lei av denne oppgaven nå, men jeg skal altså løse Euler-problem nummer 1 i Fantom også. Koden nedenfor viser blant annet hvordan man definerer en klasse med en main-metode, og hvordan lambda-uttrykk ser ut i dette språket. Jeg har valgt en funksjonell, stream-basert løsning denne gangen med bruk av en range, filtrering og aggregering.

10 class Euler1
11 {
12   static Bool MultipleOf(Int n, Int[] dividents)
13   {
14     return dividents.reduce(false) |Bool result, d->Bool|
15     {
16       result.or(n % d == 0)
17     }
18   }
19 
20   static Void main()
21   {
22     echo((1..999)
23       .toList()
24       .findAll |n->Bool| { MultipleOf(n, [3, 5]) }
25       .reduce(0) |Int memo, n->Int| { memo + n } )
26   }
27 }

Måten jeg har implementert MultipleOf-metoden på er både overkill og lite lesbart. Men når du har løst den samme oppgaven fjørti ganger begynner du etterhvert å eksperimentere litt. :)

I linje 16 utnytter jeg det faktum at alt, også boolske verdier, er objekter i Fantom. Jeg kaller derfor metoden "or" for å kombinere to boolske uttrykk. Men frykt ikke, alle de vanlige operatorene du er vandt til, som && og ||, finnes også.

Samtidighet

Samtidighet er det området hvor Fantom skiller seg mest fra Java og C#. Mens Java og C# i bunn og grunn baserer seg på en modell med delt minne mellom tråder, hvor man må synkronisere tilgang til data med locks for å forsøke å unngå deadlocks og race conditions, så tillater ikke Fantom deling av data. Det vil si, Fantom tillater ikke deling av data som kan endres (mutable state).

I stedet har Fantom to alternative teknikker for samtidighetsprogrammering.

Det første er data som ikke kan endres – immutability. Det er dette som har gjort de funksjonelle språkene som f.eks. Clojure og Haskell populære. Ikke-muterende data er bygget inn i Fantom, og kan trygt og effektivt deles mellom tråder.

Den andre teknikken er message passing. Dette er det som har gitt språket Erlang sin fremgang de siste årene. Fantom har et actor API som er bygget på ideen om å sende meldinger mellom asynkrone arbeidere.

Jeg har implementert en løsning til på Euler-oppgaven for å illustrere hvordan man kan bruke actors. Jeg beklager at koden inneholder en del repetisjon, men jeg tror det får frem poenget mitt på en ok måte.

I løsningen nedenfor oppretter jeg fire actors. Den første (summer) tar imot vilkårlige tall, legger dem til en sum som den holder rede på, og returnerer summen. De tre øvrige deler på å finne alle tallene som skal være med i løsningen, og sender disse til summer.

Til slutt spinner jeg inntil de tre arbeiderne er ferdige, og så sender jeg en siste melding til summer for å få tilbake svaret.

10 using concurrent
11 
12 class Euler1
13 {
14   Void main()
15   {
16     pool := ActorPool()
17 
18     summer := Actor(pool) |Int change->Int|
19     {
20       sum := (Int)Actor.locals.get("sum", 0) + change
21       Actor.locals["sum"] = sum
22       return sum
23     }
24 
25     multiplesOf3Actor := Actor(pool) |msg|
26     {
27       for (n:=1; n<1000; ++n)
28         if (n % 3 == 0) summer.send(n)
29       echo(msg); return null
30     }
31 
32     multiplesOf5Actor := Actor(pool) |msg|
33     {
34       for (n:=1; n<1000; ++n)
35         if (n % 5 == 0) summer.send(n)
36       echo(msg); return null
37     }
38 
39     multiplesOf15Actor := Actor(pool) |msg|
40     {
41       for (n:=1; n<1000; ++n)
42         if (n % 15 == 0) summer.send(n * -1)
43       echo(msg); return null
44     }
45 
46     f1 := multiplesOf3Actor.send("All multiples of 3 added!")
47     f2 := multiplesOf5Actor.send("All multiples of 5 added!")
48     f3 := multiplesOf15Actor.send("All multiples of 15 removed!")
49     echo("Done sending startup messages")
50 
51     // Spin-Wait for work to finish
52     while(true)
53       if(f1.isDone.and(f2.isDone.and(f3.isDone)))
54         break
55 
56     echo("The answer is ${summer.send(0).get}")
57   }
58 }

Når jeg kjører koden ser det ut som dette:

C:\code>c:\fantom\bin\fan.exe euler1_with_actors.fan
Done sending startup messages
All multiples of 15 removed!
All multiples of 3 added!
All multiples of 5 added!
The answer is 233168

Her ser du at i dette tilfettet ble multiplesOf15Actor ferdig før multiplesOf3Actor. Det er selvfølgelig overkill å bruke fire tråder til å beregne summen av alle tall som er multipler av 3 eller 5 under 1000, men actor model er en veldig spennende måte å kode på for mange andre problemområder.

Hvordan komme igang

Fantom.org er språkets offisielle side, og her finner du alt du behøver for å komme igang. Under "Docs" finner du utførlige manualer, eksempelkode og full API-dokumentasjon.

Merk at selv om Fantom er et relativt ungt språk så finnes det allerede ganske god editor/IDE-støtte for språket. FantomIDE er bygget på Netbeans, F4 er et annet som baserer seg på Eclipse, og du får støtte for Fantom i TextMate, TextPad, Emacs, Vim og flere andre steder.

Fantom inkluderer også en interaktiv REPL (Fansh), innebygget funksjonalitet for automatisert enhetstesting (Fant), og en egen build-motor som baserer seg på buildscripts skrevet i Fantom. Det har blitt lagt ned mye bra arbeid i dette språket, og det blir spennende å se hvordan det utvikler seg videre.


comments powered by Disqus