Her kan du lese om diverse utviklingsprosjekter jeg driver med – hobbyprosjekter stort sett, inkludert open source prosjekter jeg har startet eller bidrar på.

Ropy

Tuesday, January 1st, 2013
2 kommentarer

Hva er dette her?

image

Det du ser over er kildekoden til et program. Språket som er brukt heter Ropy, og programmet skriver som seg hør og bør ut teksten “hello, world”.

Ropy er altså et programmeringsspråk. Et ganske lite et. Og ganske ubrukelig egentlig, selv om det strengt tatt er turing-komplett, og dermed like “kraftig” som alle andre språk. Faktisk er det et forsøk på å lage en type språk vi kaller for en turing tarpit:

Beware of the Turing tar-pit in which everything is possible but nothing of interest is easy.

Alan Perlis, Epigrams on Programming

Jeg lagde Ropy i løpet av et par kvelder i desember. Språket er mitt bidrag til desember-konkurransen på PLT Games (Programming Language Theory Games). Hver måned blir man bedt om å lage et nytt programmeringsspråk, og i den påfølgende måneden vil man kunne stemme på hvilket bidrag man synes er best for å avgjøre en vinner. Språkene skal bedømmes i forhold til innovasjon, kompletthet og hvor godt de følger det valgte temaet for den måneden.

Hvis du har fulgt denne bloggen en stund så vet du at det er én oppgave jeg alltid pleier å løse: Å finne summen av alle tall under 1000 som er delelige på 3 eller 5. Og her er løsningen i Ropy:

image

Ropy er et stack-basert språk. Programmet begynner å kjøre oppe til venstre, og følger så tegnene. Når programmet har flere steder det kan gå – når “tauet” deler seg – velges den første veien i klokkeretningen. Men hvis verdien øverst på stacken er “0″ velger den vei mot klokkeretningen. Med dette kan vi simulere både if og løkker.

Den erfarne leser vil se at Ropy er inspirert av Befunge, som jeg presenterte i julekalenderen 2011.

Ropy-tolkeren er skrevet i Ruby, og er på godt under 200 linjer. Du kan ta en titt på den her, og vil du teste den selv er det bare å laste ned. Du finner også en eksekverbar spesifikasjon som vil hjelpe deg å forstå hvordan språket virker.

Ropy community

Kort tid etter at jeg hadde gjort Ropy tilgjengelig på Github fikk jeg også en pull requestSteffen Hageland, kjent som etse, hadde utvidet språket. Så jeg er tydeligvis ikke alene om å være helt sprø :)

Her er et eksempelprogram skrevet av Steffen:

image

Jeg trenger din stemme!

Som sagt er Ropy laget som et bidrag til en konkurranse. Og da hadde det selvsagt vært gøy om jeg fikk noen stemmer. Alle kan registrere seg for å være med å avgjøre vinneren (du må ha en github-bruker), og hvis du vil gi meg eller Ropy litt “cred” på den måten blir jeg veldig glad.

Får jeg litt stemmer kan det kanskje inspirere meg til å delta flere ganger også. Kanskje det blir 12 egenproduserte programmeirngsspråk å presentere på bloggen i år?! I januar skal det konkurreres om å lage et språk som fokuserer på automatisert testing. Ideer mottas med takk!

De viktigste lenkene

Noen utvalgte bidrag fra PLT Games du kan ta en titt på:

  • Colossal – som gjør programmering om til et tekstbasert adventure-spill.
  • nb – utfører programmer gjennom å simulere planet-baner i verdensrommet.
  • Turtle Graphics – Klassisk Turtle, turing tar-pit style. Test ut her!
  • turipong – utfører programmer ved å simulere en pong-ball som spretter fra vegg til vegg.
  • Hugo – et språk som stort sett består av GOTO-statements, og som har en veldig morsom README.
  • Seeker – programmene er node-grafer.
  • wc3pl – et språk inspirert av Warcraft III
  • cyprus“modeled as chemical reactions happening within protocellular constructions, producing new chemicals”
  • Ape – et interessant, minimalistisk språk implementert i noen få linjer F# av en Microsoft-ansatt språk-nerd.

Event sourcing med s-expressions

Sunday, June 10th, 2012
2 kommentarer

Helt siden i september 2009 (da Greg Young var på besøk i Bergen) har jeg tenkt på event sourcing. Og i årene etter har dette blitt en mer og mer populær arkitektur blant de softwareutviklerne som er mest cutting edge.

Men jeg har ikke bygget noe med det i tiden som har gått siden da. Å treffe Greg igjen på NDC i år har derimot stimulert meg på nytt, og her følger en blogpost som forklarer hva det går i, og som implementerer et lite eksempel.

Blogposten er også en liten leksjon i programmeringsspråket Clojure. Eksempelet er en nedskalert begynnelse på et verktøy jeg kommer til å lage for eget bruk, og dette språket er ideelt for oppgaven. Men kunnskap om Clojure er absolutt ikke nødvendig for å henge med i det jeg gjør, alt du behøver er et åpent sinn.

Hva er Event Sourcing?

Event sourcing består av to steg:

  1. Man lagrer alle endringer i et system som en sekvens av hendelser.
  2. Man bygger opp igjen systemets tilstand fra disse registrerte hendelsene.

event_sourcing

Se for deg hvordan en bank overfører penger fra en konto A til en konto B. De gjør ikke som i de typiske kodeeksemplene hvor man har en databasetransaksjon, og oppdaterer saldoen på de to kontoene i én atomisk operasjon. Nei, saldoen i banken din består av en serie med hendelser:

  1. Konto opprettet, saldo kr 0,-
  2. Innskudd a kr 30 000,-
  3. Overføring fra konto X a kr 560,-
  4. Betalt regning a kr 1 499,-
  5. 1,1% renter effektuert
  6. Debetkort belastet kr 19.50
  7. osv…

Selv om den ikke står der eksplisitt er det enkelt å beregne saldoen.

På grunn av at man kan bygge opp igjen systemets tilstand fra hendelsene kan man ofte klare seg uten noen annen form for lagring av tilstand. I praksis vil man nok ofte likevel lagre data i et annet format for eksempel for rapportering, og man vil også ta “snapshots” av tilstand for å slippe å kjøre gjennom hele loggen hver gang man trenger tilstand. Men rapportene og snapshotene kan når som helst forkastes og genereres på nytt fra loggen.

Fordelene med event sourcing

Fordelene med event sourcing er mange, men det kan være et komplekst tema å diskutere. Jeg har ikke mye erfaring, så jeg vil ikke gå i dybden, men her er de viktigste punktene slik jeg ser det:

  • Event sourcing innebærer såkalt append only data storage – man behøver ALDRI oppdatere data. Dette løser flere problemer, spesielt knyttet til transaksjoner og samtidighet, og skrivehastigheten til systemet kan øke dramatisk. Skulle samtidighetskonflikter mellom endringer snike seg inn så er det også mye enklere å rydde opp i dem med en hendelseskø.
  • Et system som baserer seg på event sourcing er godt tilrettelagt for testing. Man kan simulere all mulig adferd ved å sette opp en sekvens av hendelser. Og opplever man problemer i produksjon kan feilene alltid gjenskapes ved å kjøre hendelsesloggen derfra. Dette er gull!
  • Hendelsesloggen inneholder ikke bare hvilke dataendringer som har skjedd, men eksplisitt hvorfor de skjedde. Meningen bak tilstandsendringene blir tatt vare på. Dette er noe som mangler i de fleste “vanlige” systemer i dag, og er verdifullt av mange grunner. En av dem er at man etter at systemet er i produksjon kan komme opp med nye måter å analysere bruken på, og så kjøre disse analysene på historiske hendelser.
  • Og finner man en bug i systemet; da fikser man den, ruller systemet tilbake, og alle hendelsene blir registrert på nytt med den nye koden. Feil i data vil “fikse seg selv”.

Hvorfor (og hva er) s-expressions?

For noen år siden var XML det ultimate dataformatet. Nå føles det som om JSON er blitt vårt standard format, både for lagring og for kommunikasjon mellom tjenester. Men det har lenge funnes et annet format i denne kategorien, et format som i sin tid ble utviklet for å representere både kode og data (i form av lister og trær) i programmeringsspråket LISP. Dette er såkalte symbolske uttrykk, gjerne forkortet til sexprs eller sexps.

Sexps er enkle å parse, mye mindre bråkete enn XML, og mere fleksible enn JSON. Du ser ikke mye til dette formatet utenfor Lisp-sfæren, men det er ekstremt kraftig. Hvorfor? Fordi de kan representere data og programkode på en og samme tid.

I denne blogposten skal jeg implementere en del av et todo-program. I programmet skal man kunne opprette todo-items, flytte dem rundt mellom ulike “bøtter”, markere dem som utført, slette dem, og lignende handlinger.

Hva om jeg lar todo-programmet logge alt som skjer til en fil, og jeg logger på et format som dette:

(todo-created (id t1) "Experiment with event sourcing s-expressions")
(todo-created (id t2) "Draw some good event sourcing illustrations")
(todo-created (id t3) "Blog about event sourcing")
(todo-moved   (id t1) (todo-bucket today))
(todo-moved   (id t2) (todo-bucket tomorrow))
(todo-moved   (id t3) (todo-bucket tomorrow))
(todo-done    (id t1))
(todo-created (id t4) "Create an awesome todo system")
(todo-moved   (id t4) (todo-bucket in-two-days))
(todo-created (id t5) "Rule the World!")
(todo-moved   (id t5) (todo-bucket later))
(todo-deleted (id t5) (reason "Just kidding"))

Det du ser over er kun en tekstfil. Men loggelinjene er formatert som sexps. Altså er de gyldige Lisp-uttrykk. Hva om jeg så implementerte funksjoner som het todo-created, todo-moved osv., og så kjørte loggfilen gjennom en Lisp-tolker – f.eks. Clojure?

Loggen vil da ha blitt til et program skrevet i et domenespesifikt språk som gjenskaper tilstanden som hendelsene loggen er generert ut ifra en gang før skapte.

Det kan tenkes du bør lese den forrige setningen mer enn én gang!

En implementasjon i Clojure

Så la oss skrive litt kode som er i stand til å gjøre dette. Jeg begynner med å deklarere et navnerom, og oppretter så en variabel todos (globalt i navnerommet). Denne peker til en hash / dictionary / lookup table som vil holde programmets todo-items.

(ns worklog.core)

;; Todo items are stored here
(def todos (atom {}))

Deretter begynner jeg å skrive implementasjoner for nøkkelordene i loggfilen. Jeg tar de enkleste først – dette er små makroer som transformerer symbolene de omkranser til spesifike datatyper i Clojure (henholdsvis string for id og keyword for todo-bucket). Reason, som brukes i siste linje i loggfilen, tar en streng og ikke et symbol som parameter, og returnerer den derfor bare som den er. Dette er et eksempel på en funksjon som bare eksisterer for å gjøre loggfilen mere leselig.

(defmacro id [id]
  (str id))

(defmacro todo-bucket [bucket-id]
  (keyword bucket-id))

(def reason identity)

Og så har jeg kommet til den første seriøse funksjonen, nemlig todo-created. Parametrene er en todo-id og en todo-beskrivelse. Hvis et todo-item med gitt id allerede finnes i todos kaster den et exception. Hvis ikke legger den til et nytt objekt i en default bøtte (staging) og med default tilstand (open):

(defn todo-created [id description]
  (if (@todos id)
    (throw (Exception.
             (str "Todo with id " id
                  " already exist, can't create!")))
    (swap! todos assoc id { :id     id
                            :desc   description
                            :bucket :staging
                            :state  :open })))

Før jeg implementerer de resterende tre funksjonene trenger jeg en liten makro – dette er for å unngå at jeg repeterer meg selv i koden. De tre funksjonene skal nemlig oppdatere et todo-item, så da lager jeg en makro jeg kaller update-todo! som gjør det for en gitt todo-id, et feltnavn, og en verdi.

Makroen kontrollerer at et todo-item med den gitte identifikatoren eksisterer, og kaster et exception hvis det ikke gjør det:

(defmacro update-todo! [id key val]
  `(if-let [todo# (@todos ~id)]
     (swap! todos assoc-in [~id ~key] ~val)
     (throw (Exception.
              (str "Unable to find todo " ~id)))))

OBS: Nærmere gjennomlesning fikk meg til å innse at jeg her har laget en makro helt unødvendig – dette kunne ha vært en helt vanlig funksjon. Jaja..!

Når jeg har update-todo! er det trivielt å implementere funksjonene for å flytte, markere som utført, og å slette et todo-item:

(defn todo-moved [id bucket]
  (update-todo! id :bucket bucket))

(defn todo-done [id]
  (update-todo! id :state :done))

(defn todo-deleted [id _reason-ignored]
  (update-todo! id :state :deleted))

Den siste funksjonen illustrerer også et case hvor loggfilen inneholder informasjon som jeg ikke behøver for å gjenskape programmets tilstand – nemlig årsaken til hvorfor et todo-item ble slettet.

På tide å laste event-historikken

Alt jeg nå trenger å gjøre for å laste todo-listen min er å laste logfilen og eksekvere den. Følgende funksjon gjør det (den er egentlig bare en wrapper som gir meg en funksjon med et litt mere spesifikt navn):

(defn load-events-from-file [file]
  (load-file file)
  :ok)

Hvis jeg nå fyrer opp en REPL i terminalen min og laster DSL-koden så kan jeg kjøre funksjonen og gi den stien til loggfilen min (altså filen gjengitt i toppen av bloggposten):

worklog.core=> (require 'worklog2.core :reload)
nil
worklog.core=> (load-events-from-file "./todo.log")
:ok

Å skrive ut todo-listen

load-events-from-file svarer med nøkkelordet “ok”. todos-variabelen vil nå inneholde todo-itemene mine. Vi kan se på innholdet direkte, men i stedet tar jeg meg tid til å vise deg litt kode jeg har skrevet for å presentere listen litt bedre.

Funksjonene er små og har gode navn, så se om du klarer å skjønne hva de gjør (det kommer ingen forklaring):

;; Formating functions to print todo list

(defn todo-state-to-str [t]
  (condp = (:state t)
    :done    "DONE"
    :deleted "XXXX"
    :open    "    "))

(defn todo-2-str [t]
  (str "  [" (todo-state-to-str t)  "] "
       (:desc t)))

(defn todos-for-bucket [b todos]
  (filter #(= (:bucket %) b) todos))

(defn bucket-to-str [b-id]
  (let [s (name b-id)]
    (str (.toUpperCase (subs s 0 1))
         (.toUpperCase (subs s 1)))))

(defn print-bucket [id todos]
  (println (bucket-to-str id))
  (doseq [t (todos-for-bucket id todos)]
    (println (todo-2-str t))))

(defn print-todos
  ([] (print-todos (vals @todos)))
  ([t]
    (println "--- TODO-LIST ----------------------------")
    (doall (map #(print-bucket % t)
                [:today :tomorrow :in-two-days :later]))
    'EOF))

Om jeg nå går tilbake til REPL’en og evaluerer funksjonen print-todos så får jeg en formatert utskrift av todo-listen min slik den ble etter at jeg lastet inn alle eventene fra todo.log:

worklog.core=> (print-todos)
--- TODO-LIST ----------------------------
TODAY
  [DONE] Experiment with event sourcing s-expressions
TOMORROW
  [    ] Blog about event sourcing
  [    ] Draw some good event sourcing illustrations
IN-TWO-DAYS
  [    ] Create an awesome todo system
LATER
  [XXXX] Rule the World!
EOF

Som du kan se er alle items flyttet inn i riktige bøtter, dagens todo-item er utført, og verdensherredømme er kansellert.

Konklusjon

Du har sett meg fortelle om event sourcing, som er en teknikk hvor man lagrer alle hendelser i et softwaresystem, for så å kunne gjenskape nåtilstand (eller tilstanden for et hvilket som helst tidspunkt) ved å kjøre hendelsene på nytt. Dette eliminerer mer eller mindre behovet for klassisk lagring av tilstand, og gir en rekke nye muligheter.

Jo mer jeg tenker på disse mulighetene, jo mer verdifull blir denne teknikken for meg. Jeg kan f.eks. veldig enkelt lage ulike versjoner av DSL’en / parseren som utfører ulike ting basert på hendelsene i loggen. Mulighetene rundt testing, både hva angår enkelhet og styrke, er også veldig interessante.

Se ikke bort ifra at jeg kommer til å begynne å logge i sexps-format overalt fremover. Du er herved advart! :D

Opus Polyglotis II: Clojure

Thursday, March 1st, 2012
Ingen kommentarer

act4

OPUS POLYGLOTIS II, Act 4
Tagline: "Groteskt vakkert!"
På scenen: Clojure

Jeg har presentert 3 ulike programmer som gjør det samme – i Python, Ruby og Rebol – og her kommer den aller siste implementasjonen for denne gang.

Clojure er et herlig språk, men det kan være vanskelig å lese koden – spesielt når man ikke har skrevet den selv. Jeg vil likevel anbefale deg som er interessert i programmeringsspråk å ta en titt, og kanskje forsøke å sammenligne fremgangsmåten med de andre løsningene. Men jeg kommer ikke til å forklare alle detaljene, så hvis du vil at jeg skal utdype noe av koden ytterligere får du legge igjen en kommentar.

Litt om løsningen

I denne løsningen har jeg valgt å bruke multimethods for dispatch, dvs. for å velge hvilken strategi jeg skal bruke (les introduksjonen om du ikke vet hva programmet skal gjøre). Multimethods brukte jeg også nylig i artikkelen Template Method del 4: Multippel arv, og lengre tilbake i tid viste jeg teknikken i posten 1-2-3 Dispatch.

Jeg definerer også et par datatyper med DEFRECORD, og det er en av disse datatypene multimetodene fungerer på. I prinsippet er denne formen for dispatch det samme som jeg implementerte i Ruby og Rebol, bare at det er innbakt i språket, og at jeg dermed slipper å gjøre så mye av jobben selv.

Clojure-koden viser også litt interop med noen Java-klasser; jeg bruker java.util.Scanner til å sjekke om en streng er et tall (muligens overkill, men jeg hadde lyst til å teste dette objektet som jeg aldri før hadde benyttet).

Og så har jeg laget en aldri så liten regex table lexer til å parse CSV-filen. Geeks synes det er gøy med lexing!

Koden: Parsing av CSV-fil

Først deklarerer jeg et namespace, og inkluderer bibloteker jeg trenger i programmet:

 22 (ns polyglotis
 23     (:use clojure.contrib.json)
 24     (:require clojure.string)
 25     (:require [clojure.xml :as xml])
 26     (:import [java.util Scanner Locale]))

Og så definerer jeg datatypene jeg skal jobbe med:

 28 (defrecord OpusData [headers records])
 29 
 30 (defrecord OpusFormatResult [format data output])

Så kommer lexeren. Og dette er ganske vakkert, synes jeg selv. TOKENIZE splitter opp en semikolonseparert tekststreng ved hjelp av tre regulære uttrykk:

 32 (defn get-first-token [s]
 33   (first (or (re-seq #"^([^;\"]+)" s)    ; unqouted
 34              (re-seq #"^\"([^\"]*)\"" s) ; quoted
 35              (re-seq #"^;" s))))         ; separator
 36 
 37 (defn tokenize
 38   ([input] (tokenize input []))
 39   ([input tokens]
 40     (if-let [token (get-first-token input)]
 41       (if (= token ";")
 42         (recur (subs input 1)
 43                tokens)
 44         (recur (subs input (-> token first count))
 45                (conj tokens (second token))))
 46       tokens)))

Jeg trenger også en liten funksjon som kan splitte linjer; igjen bruker jeg regex, og sørger for å håndtere både unix og windows-format:

 48 (defn split-lines [text]
 49       (clojure.string/split text #"\r?\n"))

Nå bruker jeg alt det jeg har laget til å definere en funksjon som tar inn et filnavn, leser filen, splitter den opp i linjer, splitter linjene opp i tokens, og returnerer en OpusData-instans:

 51 (defn get-csv-data [filename]
 52   (let [data (->> filename          ; filename
 53                   slurp             ; Read the file content
 54                   split-lines       ; Split on rows/newline
 55                   (map tokenize))]  ; Split on fields
 56 
 57     ; Create record of headers and rows
 58     (OpusData. (first data) (rest data))))

Hvis jeg nå evaluerer (get-csv-data “data.csv”) i REPL’en, vil det gi følgende output (data.csv er den testfilen jeg har brukt gjennom hele denne bloggserien):

 60 ; (get-csv-data "data.csv")
 61  #:polyglotis.OpusData{ :headers ["Field 1" "Field 2" "Field 3"],
 62                         :records (["Value 1" "Value 2" "123"]
 63                                   ["Value A" "Value B" "456"]
 64                                   ["Value x" "Value y;;" "789.45"])}

Koden: Dispatch

Nå har vi kommet til multimetodene. Først definerer jeg APPLY-FORMAT. Når denne blir kalt så vil den velge en funksjon basert på :format-verdien til argumentet sitt (argumentet kommer til å være av typen OpusFormatRecord, definert tidligere):

 69 (defmulti apply-format :format)

Implementasjonen av det første konkrete formatet ser slik ut:

 71 (defmethod apply-format :yaml [csv]
 72   (assoc csv :output "YAML not yet supported!"))

YAML-metoden setter :output-feltet i csv-objektet til “YAML not yet supported”.

Da er vi klare for litt mere funky greier – her er definisjonen av XML-formatet. Begynn med å lese kommentarlinjene mot slutten av methode, og se om du kan nøste deg bakover og forstå hvordan det fungerer:

 75 (defmethod apply-format :xml [csv]
 76   (let [headers (->> csv :data :headers
 77                      (map #(.replace % \space \_)))
 78 
 79         xml-str #(with-out-str (xml/emit-element %))
 80 
 81         <tag> (fn [k v]
 82                 {:tag (if (keyword? k) k (keyword k))
 83                  :content (if (seq? v) (vec v) [v])})
 84 
 85         row-<tag> #(->> % (map <tag> headers)
 86                           (<tag> :record))]
 87 
 88     (->> csv :data :records      ; Pluck rows from CSV
 89          (map row-<tag>)         ; Create tags from them
 90          (<tag> :records)        ; Wrap in a <records> tag
 91          xml-str                 ; Convert to a string
 92          (assoc csv :output))))  ; Update output field

Clojure har ganske grei støtte for å konvertere hash-maps (disctionaries, om du foretrekker det navnet) til XML, og dette benytter jeg her.

Og da har vi bare ett format igjen, nemlig JSON:

100 (defmethod apply-format :json [csv]
101   (let [headers (-> csv :data :headers reverse)
102 
103         ; Fn: Coerse to Int or Float if possible
104         coerse-num #(let [s (Scanner. %)]
105                       (.useLocale s (Locale. "en" "US"))
106                       (cond
107                         (.hasNextInt s) (.nextInt s)
108                         (.hasNextFloat s) (.nextFloat s)
109                         :else %))
110 
111         ; Fn: For all rows, for all fields, coerse num
112         coerse-num-all (partial map
113                          (partial map coerse-num))
114 
115         ; Fn: Convert seq into map with headers as keys
116         mapify-row (comp (partial zipmap headers) reverse)
117 
118         ; Fn: Convert all seqs to maps
119         mapify-row-all (partial map mapify-row)]
120 
121     (->> csv :data :records     ; take the rows
122          coerse-num-all         ; coerse the numbers
123          mapify-row-all         ; make maps
124          json-str               ; convert maps to json
125          (assoc csv :output)))) ; update output field

Clojure har enda bedre støtte for JSON enn for XML. Igjen konverterer jeg dataene mine til hash-maps, og funksjone JSON-STR konverterer hash-maps til JSON. Dette er en generell teknikk innen funksjonell programmering: Omstrukturer dataene dine til noe som passer for det du skal løse, og så blir selve løsningen ganske enkel.

Koden: Main

Den siste funksjonen jeg definerer knytter sammen alt jeg har laget til nå. Den oppretter et OpusFormatRecord-objekt hvor dataene fra en CSV-fil mates inn vha. GET-CSV-DATA funksjonen. Jeg kaller APPLY-FORMAT, som vil kalle den riktige format-metoden, plukker resultatet og skriver det ut.

129 (defn main [out-format csv-file]
130   (->>
131       ; Create a record with CSV data and format info
132       (OpusFormatResult. (keyword out-format)
133                           (get-csv-data csv-file)
134                           nil)
135 
136        apply-format   ; Dispatch to correct format def.
137        :output        ; Pluck the output string
138        println))      ; Print it

Til slutt kaller jeg MAIN og sender inn kommandolinje-argumentene.

141 ; Kick it all off !!!
142 (main (first *command-line-args*)
143       (second *command-line-args*))

Og det var det :P

Oppsummering

Multimetoder er ideelt for denne typen dispatch mellom ulike strategier, men bortsett fra det kan jeg ikke peke på noen andre fordeler med Clojure. Annet enn at det er veldig gøy å kode Clojure da!

Da jeg implementerte dette her gikk forresten koden gjennom ganske mange refaktureringer. Selv den enkleste Clojure-funksjonen kan skrives på hundrevis av ulike måter, og det er vanskelig å bestemme seg for hva man liker best, hva som kommuniserer godt, hva som er enklest å vedlikeholde og så videre. Jeg skrev litt mer om dette problemet i Er Lisp’s fleksibilitet et problem? tidligere i år.

Nå tror jeg vi lar dette polyglotiske opuset ligge. Det har sansynligvis vært mer interessant for meg selv enn for mine lesere, men jeg håper(?) noen har satt pris på å få se noen tilnærmet fullverdige men likevel små program skrevet i diverse språk.

Hei då!

Opus Polyglotis II

Monday, February 20th, 2012
6 kommentarer

introductioFor halvannet år siden lagde jeg en blogserie jeg kalte Ping Ring, hvor jeg implementerte ett og samme program i en rekke forskjellige språk. Nå har jeg tenkt å gjøre det samme igjen; dette er mitt Opus Polyglotis del II.

Det jeg vil forsøke denne gangen er å implementere et program hvor jeg får brukt mange forskjellige elementer som kan være viktige for en som vil starte med språket. Alle implementasjonene vil inneholde:

  • Bruk av kommandolinje-parametre
  • Lesing og parsing av en semikolonseparert  fil
  • En eller annen form for looping
  • Konstruksjon og manipulasjon av strenger
  • Bruke av regulære uttrykk eller tilsvarende
  • Dispatch mellom ulike strategier
  • Og selvfølgelig utskrift til konsollet

Jeg vil i større grad enn sist gang forsøke å vise ulike måter å designe en løsning på. Målet er ikke så mye det å sammenligne språkene, men å utforske ulike løsningsmodeller. I tillegg bør programmene kunne fungere som praktiske og reelle (men likevel korte og overkommelige) nybegynner-eksempler for hvert av språkene. Derfor vil jeg i så stor grad som mulig forsøke å følge idiomene som finnes i språket.

Hva skal programmet gjøre?

Programmet jeg skal lage skal formatere data fra ett format til et annet. Det tar to parametre: første parameter avgjør output-format og andre parameter er stien til en CSV-fil som skal leses og transformeres. Formatene som skal støttes er JSON og XML. I bildet nedenfor ser du hvordan formatene ser ut.

formats

Jeg har ingen planer om å gjøre programmet ufeilbarlig eller 100% robust, men jeg skal håndtere et par spesialtilfeller. I CSV-filen kan du legge merke til at tekstverdier kan være omsluttet av hermetegn, men kun må være det om teksten inneholder semikolon. Legg også merke til at JSON-formatet skiller mellom tekst og tall, og at programmet må støtte desimaltall.

Et av hovedfokusene er å vise ulike måter å implementere ulike strategier på. Programmet bør være enkelt å utvide med flere strategier (dvs. flere output-formater). For å illustrere dette skal programmet også godta et tredje format (YAML), men svare med at det ikke er implementert enda.

flow

Det er altså ikke et veldig komplisert program dette her, men spennende nok til at det kan gi verdi både til meg selv og mine lesere. Så da håper jeg du har lyst til å følge med.., gjør deg klar for første akt!

Nyttårsforsetter for 2012

Sunday, January 1st, 2012
5 kommentarer

Da har vi feiret nyttår, og på denne tiden tenker jeg alltid mye på hvordan det nye året vil bli og hva jeg ønsker å gjøre anderledes. Jeg er vel ikke alene om det. Her skal jeg forsøke å samle tankene, og skrive ned noen forsetter – forhåpentligvis i form av konkrete mål. Ting som over tid skal gjøre meg til en bedre, sterkere og raskere utvikler. Fellesnevneren for mye av det er fokus.

skann0001

Mindre random, planløs surfing

Alt for ofte flipper jeg opp laptopen uten å vite hva jeg skal. Jeg surfer på måfå, er innom diverse forum, sjekker infoq og andre relevante aggregator-sites, facebook, twitter, og mail – uten egentlig å oppnå så veldig mye. Dette er sløs med tid! Hvis jeg er opplagt bør jeg i stedet programmere, eller lese noe jeg allerede har planlagt å lese. Er jeg på jobb bør jeg jobbe. Er jeg ikke opplagt bør jeg ikke åpne laptopen i det hele tatt.

Jeg skal ikke slutte helt med sosiale medier eller å hjelpe folk på forumene, men jeg bør begrense det kraftig – det har tatt litt overhånd.

For å få til dette bør jeg nok lage meg en plan for hvor ofte og når jeg faktisk får lov til å sjekke disse tingene. Jeg har ikke kommer opp med noe konkret her enda, men det skal jeg!

Høyere fokus på jobb

Jeg lar meg lett distrahere. Oppdager jeg noe nytt og spennende, eller får en tanke om noe som hadde vært greit å teste ut, så er det ofte vanskelig å konsentere seg om det jeg burde gjøre. Møter og email-kommunikasjon er også forstyrrende, og totalt sett gjør dette at jeg i perioder ikke er særlig produktiv.

Jeg må lære meg å administrere min egen tid og mitt fokus bedre. Pomodoro var en teknikk som hjalp noe. Men jeg trenger mer. Jeg ser for meg at jeg må strukturere arbeidsdagen – sette av tidsbokser for epost, forberedelse til møter, oppfølging av teamet og lignende. Jeg må passe på at jeg ikke må context-switche for ofte, for det er krevende for dem som driver med programmering.

Lese

Jeg har alltid vært flink til å lese, men i høst har jeg lest for lite – og som jeg sa i denne blogposten skyldes det blant annet at jeg har sluttet å reise kollektivt. Planen nå er derfor å sette av én time hver kveld til lesing. Det gjør ikke noe om jeg ikke får lest hver eneste dag, men regelen skal være at jeg leser om det ikke er noe annet jeg skal gjøre.

Fysisk trening

Vi har et fantastisk treningstilbud på jobben som jeg ikke har vært flink nok til å benytte meg av. Trening er noe jeg gjør i perioder – ofte holder jeg på i tre, fire måneder, for så å “glemme det” i en minst like lange periode. God fysisk form er viktig for å kunne holde på konsentrasjonen og for å få utslipp for stress. Dette må jeg bli bedre på.

Mestre tastataturet

Jeg har en gjennomført (eller fastgrodd) tastaturteknikk som fungerer greit nok. Jeg skriver nå opp mot 46 WPM når jeg fokuserer maksimalt (målt på typingtest.com). Men jeg skriver med få fingre, og ser mye på tastaturet.

Skrivehastighet og teknikk er viktig for å komme i god flow. Jeg ønsker derfor å lære meg og øve inn touch. Hvis jeg fokuserer på dette en halvtime hver dag i noen uker bør jeg kunne komme langt. Dette vil kunne hjelpe meg å holde bedre fokus og la tankene flyte bedre mens jeg programmerer (eller mens jeg blogger for den saks skyld).

Øve, øve, øve

Jeg er ganske flink til å trene på mine programmeringsferdigheter, og det må jeg selvsagt fortsette med. I år vil jeg forsøke å fokusere litt mer på metaprogrammering, gjerne i Lisp.

Jeg vil også forsøke å øke størrelsen litt på oppgavene jeg gir meg selv, sånn at jeg får økt sjanse til å eksperimentere med ulike design-teknikker. Jeg implementerer mye algoritmer, ting som løses på 20 til 60 linjer kode, men trenger mer trening på større ting – kanskje spesielt innenfor funksjonell programmering.

En siste ting jeg gjerne vil eksperimentere mer med er arkitekturer for å gjøre systemer “scriptbare”. Embedding av Ruby, JavaScript eller lignende runtime i applikasjoner eller tjenester. Jeg har gjort litt av dette, men vil at det skal bli en helt naturlig del av verktøybeltet mitt, slik at jeg bare kan smyge det inn når behovet dukker opp.

Et større hobby-prosjekt?

Et par ganger i året bruker jeg å starte på et eller annet større prosjekt som jeg håper kan bli noe fornuftig, men som jeg likevel vet er en stør sjanse for at ikke blir noe av. Programmeringsspråket MIST er et bra eksempel fra 2011 – det var et par interessante måneder mens jeg holdt på, men jeg ser ikke for meg at jeg kommer til å fullføre planene mine nå.

Det skremmer meg likevel ikke fra å prøve igjen. Jeg har en idé til et større prosjekt nå også, noe som kan bli ganske så gøy for mange om jeg får det til. Jeg sier ikke mer enda.., vil holde kortene litt tett til brystet en stund til. Kanskje trenger jeg noen av dere som beta-testere utpå våren eller sommeren engang. Vi får se!

Konklusjon

Som vanlig har jeg mange planer og ideer om hvordan året skal bli. Jeg har noen konkrete ting jeg skal jobbe med, og spesielt skal jeg bli flinkere til å holde fokus.

Når det kommer til bloggingen så tenker jeg bare å fortsette som før – blogge når det dukker opp noe som jeg får lyst til å dele. Jeg vil gjerne lage flere videoer, for det har vært gøy. Men det er også tidskrevende, og jeg vil ikke prioritere det fremfor det andre tingene.

Nå gjelder det bare å huske på dette her. Da er det bra jeg har denne bloggposten å komme tilbake til :)

Godt Nytt År alle sammen!!!

For 12 år siden…

Tuesday, October 18th, 2011
4 kommentarer

Wayback Machine (www.archive.org) er en fantatisk website. Der finner du rett og slett et historisk arkiv av internett, slik det så ut i går, forrige uke, i fjor, eller for ti år siden. I dag har jeg mimret litt, og tatt en titt på min første “internett suksess”, nemlig min Learn Tetris in 21 days!

LearnTetris

Dette var en site jeg lagde mens jeg studerte på Universitetet i Bergen, og den er i alle fall 12 år gammel nå. Den hadde et fullstendig psykedelisk design, og var selvfølgelig bare laget for gøy.

Men den ble ganske populær, og jeg hadde noen svært gode plasseringer på søkemotorene som var i bruk på den tiden. Yahoo! gav meg 4. plass (mener jeg å huske) om man søkte på bare tetris, og kombinerte man ordene tetris og online så eide jeg lenge førsteplassen. Selv om selve siten har vært død i årevis finner jeg fortsatt et par referanser til den på nettet, hvor folk sier de syntes den var “pissemorsom”.

Selv synes jeg i alle fall det er ganske morsomt å lese hva jeg skrev den gangen – og ikke minst svært, svært pinlig! Fra tetris-sidene kan man browse seg videre til mer personlig innhold, og der er det veldig mye rart må jeg innrømme. 1999 er heldigvis lenge siden.

Her er hvordan jeg beskrev meg selv den gangen:

I'm a 23 year old guy, I wear nail polish (I'm straight by the way), have purple hair, and can usually be found jumping up and down at parties screaming "...unt die Sunne Sheeeeeeiiiint!!!"

I'm a student of Computer Science at the University of Bergen (Norway), and do Java, Visual Basic and Delphi programing, Active X, HTML, CSS, SQL, DBMS and other fancy acronymes, I work with n-tier application development .. and so on (read my full personalia) ! I live behind a keyboard in Bergen, but I'm really from Haugesund - the town of the seaguls!

I felt it was time to get a life,

...since apparently they'r terribly useful. So now I also spend much of my time in movie theaters, in pubs, beeing absolutely blasted, dancing my socks off, wish I didn't stop going to the dojo, hanging out with my friends, reading Terry Pratchett books, bowling, wanting all kind of girls and drinking quite a lot of Coca-Cola - in a desperate attempt to find time to read for my exams.

This, I gather, is a good life...

Rettskriving har som du kanskje ser aldri vært en av mine sterkeste sider.

Så hvis du er interessert i å lære tetris, og har 21 dager å kaste bort, så må du gjerne ta en titt på siten min. Ikke skyld på meg om du får vondt i hodet av “tapeten”.

Mist får en side

Sunday, September 4th, 2011
6 kommentarer

Denne blogposten presenterer den nye websiden jeg har laget for programmeringsspråket Mist, samt de rundt 20 linjene Ruby-kode som skulle til for å generere siden.

Ethvert programmeringsspråk trenger en egen side – i alle fall om andre enn designeren selv skal bruke det. Og nå har jeg som sagt begynt på en side for Mist. Den har ikke så mye innhold enda, men design og struktur er på plass. Her er et screenshot (klikk for å gå til siden):

mist_page

Siden skiller seg bort fra ting jeg har gjort tidligere på flere punkter. Blant annet har jeg innsett at HTML ikke er XML, og har nå gått for mere pragmatisk HTML5, hvor jeg blant annet har droppet ting som HTML, HEAD og BODY-tags (de skal faktisk være helt unødvendige). Jeg har også brukt mye absolutt posisjonering, og til og med litt fixed. Jeg har satset på rent design og ren markup, og er ikke nervør om noen skulle velge å titte på koden.

Jeg har også oppdaget Google Web Fonts, som lar meg inkludere et hav av ulike skrifttyper på sidene uten store problemer. Eneste faren er at jeg kanskje går litt for langt, sånn som man ofte gjør når man får et nytt leketøy. Synes du jeg har gjort det?

Generering av statiske websider med Ruby

Mist-siten er hostet som Github Pages, tilknyttet Github repositorien. Sider som serves på denne måten må være statiske html-sider. Jeg ville derimot basere siten min på en template, så for å ikke måtte repetere meg selv måtte jeg generere sidene før jeg lastet dem opp.

For dette formålet støtter Github Pages et verktøy som heter Jekyll, som jeg har brukt tidligere, og som lar deg generere statiske websider basert på maler. Denne gangen valgte jeg likevel å lage mitt eget opplegg basert på kun noen få linjer Ruby.

Først lagde jeg en fil som jeg kalte genlib.rb. Den inneholder to funksjoner: Den første leser malen som er lagret i filen template.html, og returnerer den som en streng. Den andre – generate – tar inn en hash med parametre, henter ut malen, fletter inn det som er sidespesifikt i malen, og lagrer resultatet som en html-fil.

10 def template
11   template = ''
12   File.open('template.html', "r") do |file|
13     while (line = file.gets)
14       template += line
15     end
16   end
17   return template
18 end
19 
20 def generate args
21   name = args[:file] + ".html"
22   File.open(name, "w") do |file|
23     file.puts template.
24       gsub(/\{CONTENT\}/, args[:content]).
25       gsub(/\{QUOTE\}/, args[:quote]).
26       gsub(/\{TITLE\}/, args[:title])
27   end
28   puts "Generated file #{name}"
29 end

For hver siden jeg skal generere lager jeg så en ny fil som skal kalle generate-funksjonen. Nedenfor er gen_download.rb, som genererer download.html. Den definerer ønsket filnavn (linje 13), sidetittel (14), et sitat som skal brukes i toppen av siden (16-23) og selve innholdet på siden (25-33). Til slutt kaller den generate, og siden blir produsert.

10 require "./genlib"
11 
12 page = {}
13 page[:file] = "download"
14 page[:title] = "Download Mist"
15 
16 page[:quote] = <<EOF
17 <p class="quote">
18     "Language designers are not intellectuals.<br/>
19     They're not as interested in thinking as you might hope.<br/>
20     They just want to get a language done and start using it."<br/>
21     - Dave Moon
22 </p>
23 EOF
24 
25 page[:content] = <<EOF
26 
27 <h1>Download Mist</h1>
28 <p>
29   No downloads here yet! Get the source from 
30   <a href="https://github.com/tormaroe/mist">Github</a> 
31   if you're interested.
32 </p>
33 EOF
34 
35 generate page

Til slutt laget jeg en fil jeg kalte gen.rb. Den henter ut og kjører alle filer som har et navn som begynner på “gen_”. Ved å kjøre gen.rb vil jeg dermed få generert opp alle de statiske sidene mine, klare for opplasting til serveren.

1 if __FILE__ == $0
2   Dir.foreach(".") {|f| f.match(/gen_/) { load f } }
3 end

Alternativt kunne jeg har brukt Ruby ERB templating (ala klassisk ASP eller PHP), eller kanskje et annet templating system, for å generere filene. Men for mitt formål valgte jeg altså denne gangen en mere basic løsning.

Har du noen bemerkninger til Mist, webdesignet, det kommende innholdet, eller til Ruby-koden min? Nøl ikke med å legge igjen en kommentar!

Programmeringsspråket Mist

Monday, August 15th, 2011
4 kommentarer

logoEn nerd må ha noe å gjøre på. Så i sommer begynte jeg å implementere et nytt programmeringsspråk. Jeg har valgt å kalle det Mist!

Navnet ble valgt før jeg ble gjort oppmerksom på at det på tysk betyr gjødsel (for å si det pent).

Mist skal være et general purpose, dynamisk språk med fokus på den funksjonelle programmeringsparadigmen, og tilhører Lisp-familien. Det kjører på .NET-plattformen, og er implementert i C#.

Hvorfor?

Jeg begynte på dette for å lære, og for å ha det gøy. Alle utviklere med noen år på baken og respekt for seg selv burde vite hvordan man bygger et språk, og den eneste måten å kontrollere at man vet hvordan er å gjøre det.

Følelsen jeg hadde da jeg f.eks. implementerte rekursjon, eller da jeg fikk til closures, var helt hærlig. Følelsen litt senere av å forstå at jeg hadde løst scopes feil (etter å ha lest litt i Structure and Interpretation of Computer Programs), for så å rette det opp slik det burde være, var enda bedre. Jeg har allerede lært mye av dette, selv om jeg er LANGT FRA FERDIG.

Kan det brukes til noe?

Mist kommer ikke til å bli det neste, store språket som alle kommer til å ville bruke. Det kommer ikke til å kunne konkurrere med for eksempel Clojure. Men kanskje finnes det en liten nisje hvor Mist kan komme til nytte? Selv om jeg gjør dette for å lære og å ha det gøy, så har jeg i alle fall tenkt å holde på til jeg har et “ferdig” språk som kan tas i bruk av andre enn meg selv.

Mist kan brukes på flere måter; spåket er implementert i form av en høy-nivå tolker som består av én enkelt .NET dll. Denne kan refereres direkte i .NET-prosjekter for å gjøre dem “skriptbare”. Jeg har også laget en enkel Read-Eval-Print Loop hvor man interaktivt kan eksperimentere med Mist-kode, eller laste og kjøre Mist-filer.

Som en tredje opsjon har jeg tenkt å tilby en slags kompilator som tar en eller flere Mist-filer som input, og genererer en “standalone” exe-fil.

Noen betraktninger

Når man skal implementere dynamiske språk på .NET-plattformen kan det være naturlig å basere seg på Dynamic Language Runtime. Det valgte jeg derimot å ikke gjøre. Jeg ville lære mest mulig som jeg kan benytte uavhengig av plattform, og ikke bruke tid på å sette meg inn i et biblotek hvor deler av jobben allerede er gjort.

Det finnes forøvrig mange beskrivelser på nett og andre steder av hvordan man implementerer Lisp-lignende språk. Jeg valgte å ikke studere disse – mitt fokus var igjen å bruke og lære generelle teknikker for å utvikle parser, tolker og/eller kompilator (hovedinspirasjonen til prosjektet er boken Language Implementation Patternsog så trekker jeg på det jeg allerede kan om Clojure, Scheme og Common Lisp).

Ved første øyekast ser Mist ut som en hvilken som helst Lisp-implementasjon, men det skiller seg nok litt fra de fleste andre likevel. Blant annet har jeg valgt å ikke basere liste-strukturene på såkalte cons-par. I stedet bruker jeg listene i .NET basebibloteket.

Det er også vanlig at store deler av språk i Lisp-familien implementeres i språket selv. Mist består derimot av ganske mye C# – dette er for å utnytte det som allerede finnes i .NET, blant annet i et håp om å gjøre koden raskere enn jeg ellers ville klart.

Veien videre

Men det er tidlig enda. Jeg ser for meg at jeg skal jobbe med Mist – på bussen, fergen, og på kveldstid – i et par måneder til før jeg har noe som kan kalles en beta. Og estimater er til for å sprekke!

Av ting som jeg gleder meg til å gå igang med nå kan jeg nevne reader-macros, syntax quoting og tail call optimalisering. Når det gjelder tail calls så har jeg en ide om at jeg kan løse det med contiuation passing, men jeg må nok gjøre litt mer research før jeg er sikker på hvordan det skal gjøres.

Jeg har også planer om en dyp integrasjon med .NET (bruke .NET-typer fra Mist) som vil skille seg endel i fra hvordan f.eks. Clojure er integrert med Java. Med det gjenstår å se om det lar seg realisere.

Er du interessert i å ta en titt på koden, eller laste det ned og ta Mist på en liten kjøretur, så finner du prosjektet på Github. Bedre dokumentasjon på bruk og API kommer etterhvert.

Send SMS med Python

Friday, May 6th, 2011
3 kommentarer

Hvis du er en Python-utvikler som lurer på hvordan du skal få til å sende SMS-meldinger fra programmene dine, så kan du slutte å lure nå. Denne uken har jeg startet open source-prosjektet pswinpy, som er et klientbiblotek for sending av SMS. Jeg kaller versjon 0.0.1 for en beta, men det fungerer helt fint, og lar deg sende meldinger slik som dette:

1 from pswinpy import API
2 
3 api = API('black_knight','p@$$w0rd')
4 api.sendSms(4712345678, "It's just a flesh wound.")

Pswinpy tilsvarer pswincomgem-prosjektet jeg startet for Ruby-utviklere for et par måneder siden, og krever en Gateway-konto hos PSWinCom

Du kan også spesifisere avsender, bruke utsatt levering, og sende CPA- og GAS-meldinger (ta betalt for varer og tjenester) om Gateway-kontoen din støtter dette.

Noen som vil hjelpe til?

Pswinpy er mitt aller første prosjekt i Python. Det eneste jeg vet om ideomatisk Python-kode er at det aldri kan skade å inkludere en referanse til Monty Python. Derfor hadde det vært greit om noen fra Python-miljøet kunne tatt en titt på koden og se over om ting ser fornuftig ut.

“Fork” meg gjerne og foreslå forbedringer!

Jeg fikk også til slutt til å publisere pswinpy som en pakke til PyPI, den sentrale indeksen over biblotek og moduler for Python. Dette var ikke like smertefritt som å publisere til RubyGems.org – og å installere Python-pakker er heller ikke så enkelt som å installere Ruby-gems, men ikke så veldig vanskelig heller når man først har lært det.

PSWinCom gem’en har forresten blitt lastet ned over 240 ganger allerede. Jeg vet ikke hvor mange som faktisk bruker den, men synes det er bra uansett. Nå håper jeg utviklere i Python-miljøet vil flokke seg rundt pswinpy på samme måte.

Ping Ring del 8: Python

Monday, September 20th, 2010
3 kommentarer

Dette er del 8 i artikkelserien Ping Ring hvor jeg implementerer et og samme program i et utall ulike programmeringsspråk – for å se om det er noe å lære gjennom å gjøre det. Introduksjonen kan du lese her.

Etter å ha implementert Ping Ring i de programmeringspråkene som sitter sånn noenlunde i fingrene, fikk jeg lyst til å gjøre en versjon i et språk jeg ikke kunne fra før. Følgende er dermed mitt første Python-program. Som i de andre implementasjonene har jeg forsøkt å følge de idiomene som gjelder i språket, men med null erfaring er det opp til deg å vurdere om jeg har lykkes med det.

Etter litt googling og 20 minutter utvikling kom jeg frem til en fungerende Ping Ring. Det virket som om den vanlige måten å løse samtighet på i Python er å arve fra Thread-klassen, så jeg valgte å gjøre det. Implementasjonen skiller seg derfor noe fra de andre objektorienterte løsningene (C#/Ruby/Boo).

1 import sys
2 import time
3 import datetime
4 import socket
5 from threading import Thread
6
7 last_ping_time = datetime.datetime.now() # Global state
8
9 class TcpThread(Thread):
10   def __init__ (self, self_port, other_port):
11     Thread.__init__(self)
12     self.port = self_port
13     self.other = other_port
14   def get_socket(self):
15     return socket.socket(socket.AF_INET, socket.SOCK_STREAM)
16
17 class Listener(TcpThread):
18   def run(self):
19     while 1:
20       s = self.get_socket()
21       s.bind((127.0.0.1, self.port))
22       s.listen(1)
23       conn, addr = s.accept()
24       ping = conn.recv(1024)
25       self.process(ping)
26       conn.close()
27   def process(self, message):
28     global last_ping_time
29     last_ping_time = datetime.datetime.now()
30     print Received, message
31     Pinger(self.port, self.other).start()
32
33 class Pinger(TcpThread):
34   def run(self):
35     time.sleep(1)
36     s = self.get_socket()
37     try:
38       s.connect((127.0.0.1, self.other))
39       s.send(PING from %(port)s % {port: self.port})
40       s.close()
41     except Exception:
42       print *** Failed sending ping!
43
44 class Alerter(TcpThread):
45   def __init__ (self, self_port, other_port, max_delay):
46     TcpThread.__init__(self, self_port, other_port)
47     self.max_delay = datetime.timedelta(seconds=max_delay)
48   def run(self):
49     global last_ping_time
50     while 1:
51       time.sleep(5)
52       ping_delay = datetime.datetime.now() – last_ping_time
53       if ping_delay > self.max_delay:
54         print *** ALERT, RING BROKEN! + \
55             No ping in %(delay)s. % {delay: ping_delay}
56         Pinger(self.port, self.other).start()
57
58 this_port = int(sys.argv[1])
59 other_port = int(sys.argv[2])
60 max_delay = int(sys.argv[3])
61 initial_ping = sys.argv[4] == true
62
63 print ** Python Ring Server (, this_port, )
64
65 if initial_ping: Pinger(this_port, other_port).start()
66 Listener(this_port, other_port).start()
67 Alerter(this_port, other_port, max_delay).start()

På forhånd trodde jeg jeg ville like Python ganske bra. Jeg har jo litt erfaring med Python-lignende syntax fra Boo, og synes den er ganske elegant.

Det dukket imidlertid raskt opp et par ting jeg hadde problemer med å synes godt om. Dette var spesielt knyttet til scope; når man får sende inn en self-variabel i alle metoder virker det som om man har hacket inn et objekt-system i et språk som egentlig ikke er objektorientert. Å i tillegg måtte eksplisitt deklarere alle globale variabler man ønsker å benytte i en metode kan forsvares – men det minte meg om PHP, og jeg likte det ikke.

Bruk av moduler/namespace gjør meg også litt forundret. Hvorfor i all hverden må jeg si datetime.datetime.now()? Det er mulig det kan gjøres anderledes, men jeg fant dette i mange kodeeksempler.

Alt i alt liker jeg løsningen min dårlig! Designet med ulike klasser førte til alt for mye plumbing-kode. Det er mange som skryter mye av Python, men etter å ha prøvd litt (og jeg må innrømme at det er veldig lite å basere slike uttalelser på) skjønner jeg ikke helt hva de snakker om. Det slår meg at de som sier dette sansynligvis aldri har forsøkt seg på Ruby, som jeg føler er både enklere og mere sofistikert på en gang.

Bonus: Et community-bidrag..

Jeg har mottatt endel bidrag fra utviklere som har laget sine løsninger på Ping Ring i diverse språk jeg ikke behersker selv. En av disse er en 17 år gammel Python-entusiast som kaller seg oddstr13. Han har laget en fullt fungerende implementasjon med flere utvidelser i forhold til spesifikasjonen. Oddstr13′s Ping Ring sender bl.a. epost når en server går ned.

Her er et lite utdrag som viser noen av konfigurasjonsmulighetene:

334     %s [--listen=<port>] [--remote=<host>[:<port>]] [--timeout=<int>]
335    
336     –listen    Port to bind to.
337     –remote    Host to connect to.
338     –timeout   Time after last ping received before sending email.
339     –mail      Send mail on ping timeout. (Default) (Not yet implemented)
340     –nomail    Oposite of above.
341     –mailto    Address to mail. (Not yet implemented)
342     –mailfrom  Address mail apears to come from. (Not yet implemented)
343     –mailhost  Adress to smtp server. (Not yet implemented)
344    
345     (Not yet implemented) simply means that the cli argument dosn’t work
346     the functionality is however there, but you have to edit the script.

Programmer er tilgjengelig i sin helhet her.

Denne implementasjonen er på neste 370 linjer – min var under 70. Mye av koden er infrastruktur for konfigureringen og behandling av kommandolinje-argumentene. Oddstr13 har ikke brukt klasser, men har splittet opp funksjonaliteten sånn passe bra i metoder med beskrivende navn. Det at han har brukt hashtabeller (eller maps eller dictionaries eller hva de nå heter i Python) gjør endel av koden litt “bråkete”, og jeg tror jeg ville løst dette anderledes.

Men for all del, jeg er imponert og takknemlig for at han ville løse oppgaven min og spre litt Python-kunnskap. Takk skal du ha!

Flere community-bidrag, i til tider overraskende språk, presenteres snart…

Tidligere i serien: Introduksjon | C# | Ruby | Boo | Erlang | Clojure | Clojure m/Agenter.

Kildekoden fra denne blogposten er tilgjengelig på Github. Der står du fritt til å forgrene løsningen og gjøre egne modifikasjoner om du ønsker det (for å illustrere et poeng eller lignende). Som alt annet på bloggen er koden lisensiert under Creative Commons.

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!