Naast C# en Visual Basic gaat Microsoft ook F# meeleveren in Visual Studio 2010. F# is een functionele taal, zoals je kon lezen in SDN Magazine #99. Dit betekent dat waarden en functies gelijk aan elkaar zijn. Het is bijvoorbeeld mogelijk om functies als variabele mee te geven, opnieuw te definiëren of er bewerkingen op uit te voeren. Daarnaast zijn variabelen en functies binnen F# standaard ‘immutable’. Zodra een waarde of functie is toegewezen mag deze niet meer veranderen, wat weer voordelen biedt bij multithreaded programmeren. Dit heet ook wel stateless programmeren en is in puur functionele talen de manier om met variabelen om te gaan. F# is geen puur functionele taal maar een multi-paradigma taal. Daardoor ondersteunt het zowel functionele als imperatieve code.
Talen als Visual Basic en C# zijn voorbeelden van imperatieve talen. Binnen imperatieve talen vertel je stap-voor-stap wat de applicatie moet doen. Dit kan bijvoorbeeld het doorlopen van een lijst zijn. In een functionele taal beschrijf je wat je als eindresultaat verwacht zonder dat je exact specificeert wat er moet gebeuren om dat resultaat te bereiken. Dat laat je over aan de compiler en een eventueel achterliggend framework. De toenemende populariteit van multicore processoren is een van de oorzaken dat software in toenemende mate complexer wordt; er zijn immers extra mechanismes vereist zijn voor multithreading. In dit artikel wordt ingegaan op een aantal concepten binnen F# die helpen bij het omgaan met de eerder genoemde problemen. Dit doen we aan de hand van functiecompositie, datatypes en asynchrone workflows.
De exacte specificatie van het eindresultaat laat je over aan de compiler
Compositie van functies
Het gebruik van functiecomposities is binnen functionele talen een bekende manier om complexiteit te beheersen. Met deze samenstellingen kan je losse basisfuncties definiëren die samen herbruikbare onderdelen vormen voor nieuwe complexe functies. Verwar dit niet met het simpelweg aanroepen van functies vanuit een hoofdfunctie. Binnen F# is dit een rekenkundige bewerking en daardoor ook erg eenvoudig om te realiseren. Met behulp van de ComposeLeft (<<) en ComposeRight ( >>) operators kan je eenvoudig nieuwe functies samenstellen.
let SumPrices (PriceList : list<decimal>) = List.sum PriceList
let IncludeTax OrderSum = OrderSum * 1.19M
let SumAndIncludeTax = SumPrices >> IncludeTax
let Prices : list<decimal> = [10.99M; 21.50M; 45.99M;]
let result = SumAndIncludeTax Prices
Listing 1: Een kort voorbeeld van functiecompositie
Als voorbeeld combineren we de functies SumPrices(x) en IncludeTax(x), wat resulteert in functie SumAndIncludeTax(x). In figuur 1 kun je zien dat beide functies een keten vormen. De uitvoer van SumPrices(x) is de invoer van IncludeTax(x). Met andere woorden: SumAndIncludeTax(x) is gelijk aan IncludeTax(SumPrices(x)). Zodoende is het belangrijk dat alle datatypes in de keten met elkaar overeenkomen om type mismatches te voorkomen. De F# compiler controleert dit door de functietypes van alle functies met elkaar te vergelijken. Een functietype is een korte beschrijving van de in- en uitvoertypes. Dit noteren we als ‘invoertype -> uitvoertype’. In figuur 1 staat de functie IncludeTax(x) beschreven. Deze accepteert en resulteert in een decimal, dus is het bijbehorende functietype ‘decimal -> decimal’. Het functietype van de resulterende samengestelde functie bestaat uit het parametertype van de eerste functie en het uitvoertype van de laatste functie.

Fig. 1: Schematische weergave van een samengestelde functie
De F# compiler vergelijkt alle functietypes van alle functies met elkaar
Datatypes
Een veelgebruikte manier om complexiteit en compositie te beheersen binnen software zijn de concepten achter object georiënteerd softwareontwerp. Daarom ondersteunt F# types en classes, wat niet gebruikelijk is binnen functionele talen, maar dit biedt binnen F# enorme voordelen. Een F# class kan bijvoorbeeld eenvoudig toegevoegd worden aan een bestaande C# applicatie. Daarnaast kan F# code hierdoor ook gebruik maken van het .NET framework en kan het door deze voordelen dienst doen als een verrijking bovenop bestaande applicaties. Door alle multithreaded code te verplaatsen naar een F# class kunnen complexe mechanismes in het C# deel worden vermeden.
Standaard types kun je vergelijken met structs in C#. Classes heten binnen F# concrete types. Beide specificeer je met het ‘type’-keyword.
type Person =
{
Name : string;
Age : int
Weight : int
}
Listing 2: Een F# type
Zodra je haakjes noteert achter de naam van een type, is dit een class (zie listing 3).
namespace Demo
type Person() =
[<DefaultValue>]
val mutable private _name : string
[<DefaultValue>]
val mutable private _age : int
[<DefaultValue>]
val mutable private _weight : int
member public obj.Name
with get() = obj._name
and set(value : string) = obj._name <- value
member public obj.Age
with get() = obj._age
and set(value : int) = obj._age <- value
member public obj.Weight
with get() = obj._weight
and set(value : int) = obj._weight <- value
member public obj.IsNameEqual othername =
obj._name = othername
Listing 3: Een F# ‘concrete type’ / class
In het bovenstaande voorbeeld zie je drie variabelen (_name, _age en _weight), die zichtbaar zijn binnen de klasse door het private keyword. Wat opvalt, is het gebruik van het ‘mutable’ keyword. Zoals in de inleiding is beschreven, zijn alle variabelen binnen F# standaard immutable. Met het mutable keyword kan de inhoud van een variabele aangepast worden. Dat is in het geval van classes belangrijk, omdat deze vaak afhankelijk zijn van een variërende state. Een property is daar een goed voorbeeld van omdat de achterliggende waarde gedurende de uitvoer van het programma vaak kan veranderen. Indien je dus de setter van een property wilt definiëren, zul je de achterliggende waarde als mutable moeten opgeven. Binnen een functionele taal moet elke variabele altijd direct toegewezen worden. Omdat dit bij properties niet altijd mogelijk is, kun je dit gedrag beïnvloeden door het DefaultValue attribuut te specificeren bij de betreffende variabele. Het gedrag van classes en properties past eigenlijk niet binnen de denkwijze van pure functionele talen, maar F# biedt in deze gevallen het beste van beide werelden.
Variabelen zijn immutable

Fig. 2: Intellisense weergave van de Person class
Als een member geen parameters bevat, dan beschouwt F# dit als een read-only property en een member met parameters is een method. Indien je binnen properties zowel get als set wilt ondersteunen, kun je de keywords ‘with’ en ‘and’ koppelen aan de bijbehorende property.
member public obj.Weight
with get() = _weight
and set(value : int) = _weight <- value
Listing 4: Een property member
Multithreading en asynchrone workflows
In traditionele talen kan multithreaded programmeren problematisch zijn, omdat je als ontwikkelaar rekening moet gaan houden met alle bijbehorende administratie op de achtergrond. Je moet een aantal mechanismes inbouwen om te voorkomen dat problemen als deadlocks en racing conditions optreden, zoals monitors, mutexes en asynchrone methoden. Als je F# applicaties stateless ontwikkelt en gebruik maakt van immutable variabelen, zul je hier niet zo snel tegen aan lopen. Dit dwingt je namelijk om bij elke bewerking een nieuwe instantie van de betreffende variabele aan te maken. Hierop kun je vervolgens de gewenste bewerking uitvoeren (zie figuur 3). Indien je toch mutable variabelen gebruikt, zijn deze voordelen niet meer van toepassing. Je kunt jezelf echter afvragen waarom die nodig zouden zijn, gezien de voordelen die immutable variabelen binnen F# bieden.
Deadlocks kunnen ontstaan als je niet stateless ontwikkelt of mutable variabelen gebruikt

Fig. 3: Omgang met immutable state
F# bevat asynchrone workflows om parallelle verwerking te ondersteunen. Daar moet je wel het één en ander voor doen. Eerst moet de parallel uit te voeren code gedefinieerd worden met behulp van het async type. Je mag meerdere async types definiëren die elk een andere taak op zich nemen. Je kunt het vergelijken met het maken van een parallelle batchjob. Je hoeft jezelf niet bezig te houden met het maken van threads, want dat doet het F# framework allemaal voor je op de achtergrond. Zodra alle taken gedefinieerd zijn, geef je alle async types door aan Async.Parallel. Deze retourneert een collectie die uitgevoerd wordt met Async.Run. Listing 5 geeft aan hoe je async types definieert en uitvoert.
let x = async { return 10 + 20 }
let y = async { return 20 + 30 }
let asyncCollection = Async.Parallel( [x; y] )
Async.Run (asyncCollection)
Listing 5: Eenvoudige parallele uitvoer
Omdat binnen functionele talen een collectie ook mag bestaan uit functies, kun je deze ook doorgeven aan Async.Parallel. Dit kan bijvoorbeeld een for-loop instructie zijn die asynchroon een list verwerkt.
open System
let DoSqr(x) =
async { let sqr = x * x
do Console.WriteLine(sqr.ToString()) }
let result = Async.Run (Async.Parallel
[ for i in 1 .. 100 -> DoSqr(i) ])
Listing 6: Parallele verwerking van een for-loop
In het bovenstaande voorbeeld vermenigvuldigt de functie DoSqr x met zichzelf en schrijft het resultaat naar de console. Door de functie het type async te laten retourneren, geef je aan dat deze functionaliteit onderdeel is van de asynchronous workflow. Zodra de applicatie wordt uitgevoerd zie je dat de volgorde van de output willekeurig is, wat aangeeft dat de code parallel is uitgevoerd.

Fig. 4: De output van de asynchroon uitgevoerde for-loop
Conclusie
In dit artikel zie je slechts een paar voorbeelden van de mogelijkheden die F# biedt. Hierdoor kan de ontwikkelaar zich meer concentreren op het implementeren van bijvoorbeeld business rules, zonder dat deze zich hoeft te bekommeren om de bijbehorende achtergrond administratie van bijvoorbeeld threads. Asynchronous workflows zijn een goed voorbeeld hiervan. Daarnaast biedt F# interessante mogelijkheden op het gebied van compositie, zoals samengestelde functies, de volledige ondersteuning van classes en de mogelijkheid om F# projecten op te nemen in bestaande C# solutions. Dit alles moet leiden tot overzichtelijke, compactere en schaalbare applicaties, die gemakkelijk te implementeren zijn binnen bestaande software.
Links