Thursday, March 25th, 2010
Skriv en kommentar

Jeg har tidligere snakket om betydningen av å beherske både statiske og dynamiske språk. En annen akse blivende polygloter er nødt til å se nærmere på er det vi kaller ulike programmeringsparadigmer.

paradigme_tre

(Språk-treet er selvfølgelig ikke komplett, og kategoriseringen er åpen for diskusjon.)

Om du har omtrent samme bakgrunn som meg er du mest kjent med de imperative språkene, som dominerer software-bransjen. Imperativ (eller befalende om du vil) betyr at vi forteller datamaskinen detaljert hva den skal gjøre – vi manipulerer variabler for å oppnå ønsker resultat.

Jeg tror det er veldig viktig at vi også studerer og lærer oss å beherske de mere deklerative (eller beskrivende) språkene. De tvinger oss til å tenke ganske anderledes når vi skal løse problemer. Om vi til syvende og sist likevel benytter oss av et imperativt språk så vil det vi lærer gi oss nye perspektiv som hjelpe oss til å løse oppgavene våre bedre. Dessuten er det mange språk som befinner seg i flere kategorier samtidig, som støtter f.eks. både objektorientering og funksjonsorientering (som alltid er det glidende overganger).

For å illustrere forskjellene mellom et utvalg paradigmer skal vi se på ulike løsninger for å finne den største, felles divisoren mellom to tall (heretter kalt gcdGreatest common divisor). Og vi begynner med det vi kjenner best…

Imperativ programmering

1 // GCD implementert i C
2 int gcd(int a, int b) {
3   while (a != b) {
4     if (a > b) a = a – b;
5     else b = b – a;
6   }
7   return a;
8 }

Den imperative algoritmen er for anledningen implementert i C, og illustrerer hvordan befalende kode fungerer:

For å beregne gcd av a og b, undersøk om a og b er like. Hvis så er tilfelle, returner en av dem. Hvis ikke, bytt den største av dem med forskjellen mellom dem og repeter.

Funksjonsbasert programmering

1 ; GCD implementert i Scheme
2 (define gcd
3   (lambda (a b)
4     (cond ((= a b) a)
5           ((> a b) (gcd (- a b) b))
6           (else (gcd (- b a) a)))))

I et funksjonelt språk som Scheme legger man vekt på det matematiske forholdet mellom input og output. Lambda-nøkkelordet introduserer en definisjon av en funksjon, og (a b) er funksjonens argumentliste. “cond” er en slags switch/case-konstruksjon. (- a b) er et kall til metoden “-” (minus) med a og b som argumenter. Her er metoden i pseudokode:

Gcd av a og b er definert til å være (1) a når a og b er like, (2) gcd av b og a – b når a er større enn b, og (3) gcd av a og b – a når b er større enn a. For å beregne gcd for to numre, ekspander og forenkle denne definisjonen til avslutning.

Rekursjon er en av hovedvirkemidlene i funksjonsbasert programmering. Samme algoritme kunne man selvsagt ha fått til i imperative språk også, selv om det er vanligere å se den første varianten. Det er derimot mye fra imperativ programmering man ikke gjør i funksjonsbasert. Fraværet av (det vi for enkelhets skyld kan kalle) variabel-manipuleringen gjør at funksjonsbasert kildekode i større grad kan analyseres for korrekthet. Det er også enklere å automatisere enkelte optimaliseringer, som å parallellisere programmer.

Logisk programmering

1 % GCD implementert i Prolog
2 gcd(A,B,G) :- A = B, G = A.
3 gcd(A,B,G) :- A > B, C is A-B, gcd(C,B,G).
4 gcd(A,B,G) :- B > A, C is B-A, gcd(C,A,G).

I et logisk språk spesifiserer vi et sett med axiomer og regler som lar systemet finne løsningen. Det kan være enklere å forstå Prolog-koden om du leser “:-” som “hvis” og “,” som “og”. Her er det samme i pseudokode:

Proposisjonen gcd(a, b, g) er sann om (1) a, b og g er alle like, (2) a er større enn b og det eksisterer et nummer c slik at c er lik a – b og gcd(c, b, g) er sant, eller (3) a er mindre enn b og det eksisterer et nummer c slik at c er lik b – a og gcd(c, a, g) er sant. For å beregne gcd av to numre, søk etter et nummer g (og ulike numre c) slik at man kan bevise at gcd(a, b, g) er sant.

Konklusjon

Valget av imperativ, funksjonell eller logisk programmering får som sagt ikke bare betydning for hvordan koden blir seende ut, men også hvordan utvikleren tenker for å komme opp med en løsning. Å beherske disse måtene å tenke på vil gjøre deg til en bedre problemløser. I tillegg vil du ha flere verktøy å velge blant – noen problemer vil alltid være enklere å løse deklerativt enn imperativt.

En annen ting som er interessant med de deklerative språkene er at vi ikke tvinger datamaskinen til å jobbe på en bestemt måte. Det muligjør implementasjon av forbedringer uten å røre program-koden, f.eks. gjennom å utvikle bedre kompilatorer. Den samme effekten har vi f.eks. når vi benytter LINQ i .net; LINQ er et deklerativt subset av C#/VB.net.

Tips til Java og .Net-utviklere

Om du er Java-utvikler, eller C#-utvikler som meg, så skal du være klar over at du har tilgang til funksjonsbaserte språk på favorittplattformen din også. Scala på Java-plattformen er et språk som kombinerer objektorientering og funksjonsbasert programmering. Fra Microsoft har vi fått F#, en variant av ML som støttes fullt ut i Visual Studio 2010 – på samme måte som Scala kombinerer også F# den funksjonelle og den objektorienterte paradigmen.

Hvis du vil ha et språk som er “renere” kan du velge Clojure – en moderne (2007) Lisp-dialekt som kjører på både .Net og Java-plattformen. Er du interessert i logiske språk kan du ta en titt på P#, som gir deg Prolog i .net. Prolog Cafe er tilsvarende for Java.

For komplette lister over språk på Java Virtual Machine og Common Language Infrastructure, se her og her.

Denne blogposten var inspirert av boken Programming Language Pragmatics, hvor kodesnuttene også er sakset fra.

Kategorier: .net ninja, Polyglot.
RSS feed for kommentarene. Tilbaketråkk.

3 kommentarer til “Programmeringsparadigmer – ulike måter å tenke på”

  1. Bjarte Says:

    Interessant post. Jeg liker slike oversikter som gir grunnlag for videre undersøkelser :) Jeg er i ferd med å finne meg et passende funksjonelt språk å leke med. Tror jeg heller mot F#, men er åpen for alternativer :)

  2. Torbjørn Says:

    Jeg tror jeg skal være forsiktig med å anbefale hva du skal velge. Begynte selv først å se på Haskell, som ser omtrent ut som Scheme minus parantesene (usikker på om jeg liker koden best med eller uten). Etter at jeg fikk øynene opp for Erlang har jeg derimot skippet Haskell. Erlang er et veldig interessant språk – det er funksjonsbasert, men har lånt aller mest fra Prolog, så det er en slags bastard.

    Er du veldig glad i .NET, og har lyst å få noe praktisk ut av det du lærer så raskt som mulig, er sikkert F# et av de bedre valgene. Min begrunnelse for å ikke velge det er at jeg vil ha et \”rent\” språk som tvinger meg å følge paradigmen – da tror jeg at jeg lærer mest.

  3. Ameth Says:

    Det er ikke akkurat enten-eller: http://augustss.blogspot.com/search/label/BASIC (BASIC-implementasjon i Haskell)

    Hvis det er Haskell valget faller på er dette en ganske god nybegynnerguide: http://learnyouahaskell.com/

Skriv en kommentar

Tillatte tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Siste kommentarer

Torbjørn
PS: Takk til Børge Hansen, som delte SCARF-modellen med meg!...
Børge Hansen
Denne likte jeg veldig godt. Du skriver godt og har gode betraktninger  Keep it up – flere trenger å tørre å lære mer om ledelse – du l...
Tormod
Er egentlig ikke overrasket. F# sin fortè er programmererens produktivitet/kvalitet og anledning til parallell kjøring. Men kjøremotoren har ...
Stian
Ville også prøvd med et større problem (x100 eller x1000 f.eks). Når man snakker så små brøkdeler av et sekund som her så kan tiden for en ell...
Torbjørn
Har ikke sjekket - tar en titt i morgen hvis tid :)...
Einar W. Høst
Mhp tco: hva sier ILSpy?...
Torbjørn
Har ikke sett noe på PSeq før, men kjenner til den typen funksjoner fra blant annet Clojure. Og problemet med slike funksjoner i sammenhenger som de...
Håvard
Veldig bra sammenligning! Har du sett på ytelsen av PSeq.* fra powerpakken? Tipper den vil gi performancehit på små mengder, men kan kanskje resul...
Torbjørn
Jeg kom på en demonstrasjon-variant til jeg burde inkludere, nemlig bruk av list comprehension (en type computation expression (også kalt monads)). ...
Einar W. Høst
Interessant, det blir en trade-off mellom eleganse og fart på en måte. Den funksjonelle løsningen med vanlig filter er ren og pen, mens den imperat...
Creative Commons-lisens
Innholdet på denne bloggen er tilgjengelig under Creative Commons Navngivelse-Ikkekommersiell-DelPåSammeVilkår 3.0 Norge lisens.

Programmeringsbloggen
Kjempekjekt.com

© 2006-2013 Torbjørn Marø

Jeg har vært en profesjonell programmerer siden 1999, og dette er min blogg. Målet med bloggen er å stimulere meg selv og alle andre til kontinuerlig eksperimentering og læring.

Jeg forsøker å være allsidig, og programmerer blant annet i C#, Ruby, Erlang og Clojure.

Jeg praktiserer TDD og andre smidige utviklingspraksiser. Jeg er opptatt av kvalitet og ren kode.

Dette og ganske mye mer kan du lese om på denne bloggen!