Ruby

Jeg rotet meg litt bort i programmeringspråket Ruby for noen år siden, og har lovet meg selv å komme tilbake til det nå når IronRuby blir modent på .NET plattformen. Her kan du lese det jeg har skevet om dette..

Euler #1 som et sett-problem

I forrige bloggpost presenterte jeg ulike løsninger på Euler-problem nummer 1. Men alle disse løsningene var i prinsippet like; iterer over alle tall mellom 0 og 1000, og filtrer ut og pluss sammen tallene som er multipler av 3 eller 5.

Jeg klarer ikke helt å slippe denne oppgaven. Alternativt kan vi nemlig se på det som et matematisk sett-problem, og da kan vi implementere løsninger som er litt mere deklerative. Hvis A er alle tall som er multipler av 3, og B er alle tall som er multipler av 5, så er løsningen å summere tallene i unionen av A og B.

Euler_sets

La oss ta en titt på ulike måter å implementere dette på…

A + B – snittet av a og b

Gitt at A og B er settene som beskrevet over. Da er en løsning å legge sammen summen av tallene i A og summen av tallene i B. Problemet nå er at tall som både er multipler av A og multipler av B blir lagt til i summen to ganger. For å løse dette kan vi trekke fra summen av alle disse tallene – nemlig alle tall som er multipler av 3 ganger 5, altså 15.

Her er en løsning i Ruby:

10 def sum_multiples args
11   (args[:of]..args[:upto]).
12     step(args[:of]).
13     reduce(:+)
14 end
15 
16 sum  = sum_multiples :of => 3, :upto => 999
17 sum += sum_multiples :of => 5, :upto => 999
18 sum -= sum_multiples :of => 3*5, :upto => 999
19 puts sum

Jeg liker hvor leselig koden blir: “Sum er lik sum multiples av 3 opp til 999″ osv. Men ved å introdusere litt kodeduplisering kan det hele selvsagt gjøres endel enklere:

23 puts (3..999).step(3).reduce(:+) +
24      (5..999).step(5).reduce(:+) -
25      (3*5..999).step(3*5).reduce(:+)

Det vi trekker fra på slutten er altså snittet mellom A og B.

Unionen av a og b direkte

Mange programmeringsspråk/standard bibloteker har direkte støtte for å beregne unionen av to sett, og det kan vi også benytte. Her er en tilsvarende Ruby-implementasjon som oppretter A og B, tar unionen av disse, og summerer sammen:

30 def set args
31   (args[:of]..args[:upto]).
32     step(args[:of]).
33     to_a
34 end
35 
36 multiples_of_3 = set :of => 3, :upto => 999
37 multiples_of_5 = set :of => 5, :upto => 999
38 multiples_of_3_or_5 = multiples_of_3 | multiples_of_5
39 
40 puts multiples_of_3_or_5.reduce(:+)

Nå begynner det å komme seg :) Som en referanse slenger jeg med den samme løsningen implementert i Clojure, som har en egen set-datatype:

1 (defn multiples_of [x upto]
2   (set (range x upto x)))
3 
4 (defn multiples_of_3_or_5 []
5   (clojure.set/union (multiples_of 3 1000)
6                      (multiples_of 5 1000)))
7 
8 (->> (multiples_of_3_or_5) (reduce +) println)

Som du forstår er det mange ulike måter å løse en programmeringsoppgave på – selv en så enkel oppgave som dette. Ulike språk foretrekker ulike løsninger, og det er en av grunnene til at det er interessant å studere dem.

NÅ er det på tide å gi seg! Håper jeg ikke har kjedet vettet av deg med denne oppgaven – men nå er du i alle fall skikkelig primet og klar til å studere løsninger programmert i diverse språk du sansynligvis aldri har sett før. Neste blogpost kommer 1. desember!

Hello World, Euler style

I den oppkommende adventskalenderen, hvor jeg hver dag frem til Jul vil introdusere deg for et nytt programmeringsspråk, har jeg valgt én bestemt oppgave for å vise frem språkene – nemlig den første og enkleste oppgaven på Project Euler.

Oppgaven lyder:

Hvis vi lister opp alle naturlige tall under 10 som er multipler av 3 eller 5 så får vi 3, 5, 6 og 9. Summen av disse er 23. Finn summen av alle multipler av 3 eller 5 under 1000.

Jeg bruker denne oppgaven som min Hello, World når jeg lærer meg nye språk. Den sørger for at jeg raskt lærer meg flere grunnleggende byggestener i språket, som hvordan man itererer, og hvordan man tar avgjørelser basert på numeriske operasjoner.

Jeg har også brukt oppgaven her på bloggen tidligere, i posten Parallellisering av en algoritme i Erlang. I dag gir jeg deg mulighet til å gjøre deg kjent med oppgaven, og viser noen implementasjoner i de språkene du normalt ser på programmeringsbloggen.

Løsning i C#

C# er språket jeg bruker til daglig, så det er et greit sted å begynne. Her følger en komplett løsning som looper gjennom alle tallene opp til 1000, sjekker om de er multipler av 3 eller 5 (ved å bruke modulo), og summerer opp tallene i sum-variabelen. Det siste som skjer er at tallet skrives ut.

10 class Program
11 {
12     static void Main()
13     {
14         int sum = 0;
15         for (int i = 0; i < 1000; i++)
16             if (i % 3 == 0 || i % 5 == 0)
17                 sum += i;
18         System.Console.WriteLine(sum);
19     }
20 }

Som sagt er det en meget enkel oppgave – så sant du kjenner til modulo da.

En alternativ måte å løse oppgaven er å bruke en funksjonell, stream-basert teknikk. I .NET vil dette si Linq. Her er en løsning i C# som bruker Range, Where og Sum:

10 using System.Linq;
11 
12 class Program
13 {
14     static void Main()
15     {
16         System.Console.WriteLine(
17             Enumerable.Range(1, 999)
18             .Where(i => i % 3 == 0 || i % 5 == 0)
19             .Sum());
20     }
21 }
22 

I julekalenderen vil du komme til å se en god mix av disse to fremgangsmåtene, og noen som er helt anderledes.

Løsning i Ruby

Ruby er det språket det er lettest for meg å hoppe inn i om jeg skal lage et kjapt lite verktøy, skal teste ut en algoritme eller noe lignende. Her følger en imperativ løsning:

1 sum = 0;
2 (1...1000).each do |i|
3   sum += i if i.modulo(3) == 0 or i.modulo(5) == 0
4 end
5 puts sum

I Ruby bruker jeg omtrent aldri vanlige for-løkker – å opprette en range, og så kjøre en kodeblokk for hvert element er mere naturlig.

En stream-basert løsning er mere elegant. Da bruker jeg select for å filtrere tallene, og reduce for å summere, slik som dette:

7 puts (1...1000).
8   select{|x| x.modulo(3) == 0 or x.modulo(5) == 0}.
9   reduce(:+)

Løsning i Clojure

Clojure er fortsatt favorittspråket mitt, så jeg må ta med noen løsninger i det også. Selv om det er helt unaturlig å bruke en løkke i Clojure så har jeg kokt sammen en slik løsning for denne ene gangens skyld:

 1 (loop [sum 0, numbers (range 1000)]
 2   (if (seq numbers)
 3     (let [i (first numbers)]
 4       (if (or (zero? (mod i 3))
 5               (zero? (mod i 5)))
 6         (recur (+ sum i) (rest numbers))
 7         (recur sum (rest numbers))))
 8     (println sum)))

Det fungerer, men jeg gremmes! I Clojure er stream-basert programmering det naturlige, og jeg kan bruke range, filter og reduce til å løse oppgaven ganske enkelt:

 1 (println
 2   (reduce +
 3           (filter #(or (zero? (mod % 3))
 4                        (zero? (mod % 5)))
 5                   (range 1000))))

Det er derimot ikke sikkert du synes det er så enkelt å lese denne koden. Trikset med å lese Clojure/Lisp-syntaks er som følger: Skann raskt med øynene fra venstre mot høyre til du kommer til uttrykkets siste del, som i dette tilfellet er (range 1000). Arbeid deg så tilbake mot høyre.

For å gjøre koden litt mere leservennlig tilbyr Clojure deg å gjøre det samme på følgende måte:

10 (->> 1000 range
11      (filter #(or (zero? (mod % 3))
12                   (zero? (mod % 5))))
13      (reduce +)
14      println)

Denne koden kan leses fra venstre mot høyre. “Ta tallet 1000, og lag en range av det. Filtrer med en anonym funksjon som sjekker om tall er delelig på 3 eller 5. Reduser med pluss (altså pluss dem sammen), og skriv til slutt ut”.

Løsning i Common Lisp

Og helt på tampen tar jeg med en løsning i Common Lisp – fordi man der finner den kuleste og mest komplette varianten av en for-løkke som finnes i noe språk, nemlig loop makroen:

1 (print (loop for x below 1000
2              when (or (zerop (mod x 3))
3                       (zerop (mod x 5)))
4              sum x)

Du har nå sett oppgaven løst med fire ulike språk, og i desember vil du få se flere løsninger. Da vil jeg sansynligvis bryte oppgaven opp i flere steg eller funksjoner, slik at jeg får presentert enda flere aspekter ved språkene jeg forteller om.

Jeg håper du gleder deg like mye som meg :)

Hvem bor hvor? (CodingDojo/ZipTalk)

I forrige uke hadde jeg en litt spesiell Coding Dojo / ZipTalk på jobben. Jeg startet med å gi utviklerne på teamet en oppgave, og sa at de hadde en halv time til å komme opp med en løsning i felleskap. Oppgaven er hentet fra Structure and Interpretation of Computer Programs (hvor den ble brukt til å illustrere ikke-deterministisk programmering), og lød som følger:

Baker, Cooper, Fletcher, Miller og Smith bor i samme hus med fem etasjer, men i hver sin etasje. Baker bor ikke i øverste etasje, og Cooper bor ikke nederst. Fletcher bor hverken øverst eller nederst. Miller bor høyere opp enn Cooper. Smith bor ikke i etasjen direkte over eller under Fletcher. Fletcher bor ikke i etasjen direkte over eller under Cooper.

Lag et program som forteller i hvilken etasje hver av dem bor!

Hvis du ønsker å forsøke deg på oppgaven selv anbefaler jeg at du gjør det før du leser videre!

Jeg kastet med vilje utviklerne ut på dypt vann, og hadde ikke regnet med at de ville komme i mål. Noe de heller ikke gjorde. Målet mitt med oppgaven var blant annet å gi dem erfaring med å kaste seg over en oppgave under sterkt tidspress, og se hvordan de sammarbeidet. Men det var også å presentere en alternativ måte å løse slike oppgaver på – mer om det litt lenger nede.

Da halvtimen var over snakket vi litt om oppgaven og hvordan det hadde gått, og så presenterte jeg et par løsninger på problemet i Ruby. Den første er ikke spesielt elegant, men fungerer. Prinsippet er at jeg ved hjelp av fem nøstede for-løkker genererer alle mulige kombinasjoner av etasjer for de fem beboerne. I den innerste løkken skipper jeg løsninger som ikke er korrekte i forhold til reglene i oppgaven ved å bruke next (samme som continue i C# og andre steder).

Til slutt skriver jeg ut de akseptable løsningene. Her følger koden:

10 for baker in 1..5
11   for cooper in 1..5
12     for fletcher in 1..5
13       for miller in 1..5
14         for smith in 1..5
15           all = [baker, cooper, fletcher, miller, smith]
16           next unless all.count(1) == 1
17           next unless all.count(2) == 1
18           next unless all.count(3) == 1
19           next unless all.count(4) == 1
20           next unless all.count(5) == 1
21 
22           next if baker == 5
23           next if cooper == 1
24           next if fletcher == 1 or fletcher == 5
25           next unless miller > cooper
26           next if (smith - fletcher).abs() == 1
27           next if (fletcher - cooper).abs() == 1
28 
29           puts "Baker = #{baker}"
30           puts "Cooper = #{cooper}"
31           puts "Fletcher = #{fletcher}"
32           puts "Miller = #{miller}"
33           puts "Smith = #{smith}"
34           puts
35         end
36       end
37     end
38   end
39 end

Deretter demonstrerte jeg en løsning hvor jeg utnytter litt flere av Ruby’s muligheter. I linje 10 til 16 nedenfor monkey-patcher jeg Array-klassen med litt syntakstisk sukker for å gjøre løsningen mere lesbar. Linje 27 til 34 skriver ut de gyldige løsningene.

Selve løsningen på problemet ligger i linje 18 til 25. Her bruker jeg permutation-metoden til å gi meg alle kombinasjonene av et array – det tilsvarer de 10 første linjene i løsningen over. Deretter bruker jeg find_all (tilsvarer filter i mange andre språk) til å begrense permutasjonene til de som er gyldige.

10 class Array
11   def baker; self[0] end
12   def cooper; self[1] end
13   def fletcher; self[2] end
14   def miller; self[3] end
15   def smith; self[4] end
16 end
17 
18 answer = [1,2,3,4,5].permutation.find_all do |solution|
19   not solution.baker == 5 and
20   not solution.cooper == 1 and
21   not solution.fletcher == 1 and not solution.fletcher == 5 and
22   solution.miller > solution.cooper and
23   not (solution.smith - solution.fletcher).abs() == 1 and
24   not (solution.fletcher - solution.cooper).abs() == 1
25 end
26 
27 answer.each do |a|
28   puts "Baker = #{a.baker}"
29   puts "Cooper = #{a.cooper}"
30   puts "Fletcher = #{a.fletcher}"
31   puts "Miller = #{a.miller}"
32   puts "Smith = #{a.smith}"
33   puts
34 end

Men poenget mitt med denne CodingDojoen/ZipTalken var å introdusere noe helt annet…

En løsning i prolog

Prolog er et logisk programmeringsspråk. I motsetning til Ruby, som er imperativt, er logiske språk deklerative. Det vil si at i stedet for å implementere en algoritme for å løse problemet så beskriver vi bare selve problemet, og Prolog løser det for deg!

Prolog kan se ganske gresk ut for dem som ikke har vært borti det før, men la oss bare hoppe i det. Jeg skal forsøke å forklare underveis.

 1 apartments([]).
 2 apartments([Head|Tail]) :-
 3   member(Head, [1,2,3,4,5]), apartments(Tail).

Linjene over er en regel som forteller Prolog hva en liste av leiligheter/etasjer er for noe. Linje 1 sier at en tom liste, alstå [], er en liste av leiligheter. De neste to linjene er en rekursiv regel som sier at en ikke-tom liste er en liste av leiligheter om det første elementet (Head) er et medlem av listen [1,2,3,4,5], og resten av elementene (Tail) også er en gyldig liste av leiligheter.

Tok du den?

 5 distinct([]).
 6 distinct([Head|Tail]) :-
 7   \+member(Head, Tail), distinct(Tail).

Linje 5 til 7 beskriver regelen som sier at alle må bo i forskjellige etasjer – altså at løsningen må være en liste hvor alle elementene er unike. Først sier jeg at en tom liste er ok. Deretter sier jeg at en ikke-tom liste er ok om det første elementet (Head) ikke finnes i resten av elementene (Tail), og resten av elementene også er en unik liste. Backslash og pluss betyr “ikke” i Prolog.

I den neste linjen lager jeg for enkelhets skyld en regel som kombinerer de to forrige reglene:

 9 distinct_apartments(L) :- apartments(L), distinct(L).

Og så er det på tide å fortelle Prolog hva det vil si å bo rett over eller rett under en annen beboer. I linje 11 og 12 sier jeg at X og Y bor i tilstøtende etasjer hvis absoluttverdien av X minus Y (som jeg kaller Diff) er lik 1.

11 adjacent(X, Y) :-
12   Diff is abs(X - Y), Diff = 1.

Da gjenstår det bare å lage en hovedregel som inkluderer alle detaljene fra oppgaven. I dwelling sier jeg at Baker, Cooper, Fletcher, Miller og Smith bor i hver sin etasje, at Baker ikke bor i femte, at Cooper ikke bor i første, at Fletcher hverken bor i femte eller første, at Miller bor høyere opp enn Cooper, og at Smith og Fletcher, og Fletcher og Cooper ikke bor like over eller under hverandre.

14 dwelling(Baker, Cooper, Fletcher, Miller, Smith) :-
15   distinct_apartments([Baker, Cooper, Fletcher, Miller, Smith]),
16   Baker \= 5,
17   Cooper \= 1,
18   Fletcher \= 1, Fletcher \= 5,
19   Miller > Cooper,
20   \+adjacent(Smith, Fletcher),
21   \+adjacent(Fletcher, Cooper).

Og da er vi ferdige. Fyrer jeg opp Prolog og kjører regelen min så får jeg svaret:

dwelling

Prolog sier helt til slutt “no” fordi jeg spurte om det fantes flere løsninger – og det gjorde det ikke.

Hva var poenget?

Poenget var at med Prolog så har jeg ikke behøvd å tenke på hvordan jeg skulle løse oppgaven algoritmisk. Jeg har ikke behøvd å generere en masse forslag til løsninger som jeg så må filtrere. I stedet har jeg bare reformulert oppgaven på en måte som Prolog forstår, og Prolog finner selv den korrekte løsningen for meg.

Det er dette vi kaller deklerativ programmering.

Da teamet forsøkte å løse oppgaven under sterkt tidspress klarte de ikke å komme opp med den nødvendige algoritmen. Om de hadde kunnet Prolog hadde de ikke behøvd det. Er ikke det ganske interessant?

Bergen CodingDojo

For de som er interessert i Coding Dojo konseptet vil jeg benytte anledningen til å nevne at det i disse dager er tatt initiativ til en CodingDojo i Bergen. Det er Kjerti Berg som jobber i konsultenselskapet Miles som står bak. Og målet er at folk som ønsker å bli bedre programmerere skal få øve sammen med likesinnede gjennom kode kataer i en sosial setting.

Det første møtet blir 22. november. Kommer du?

Relaterte innlegg: Coding Dojo på Contiki, ZipTalks på jobben.

loggfil-hacking med Ruby

loggfilhacking

Ruby er et perfekt språk til å lage små verktøy for å automatisere ting man som gode utviklere uansett bør være for late til å gjøre manuelt. Jeg bruker først og fremst Ruby til å lage små scripts som hjelper meg i bygging og/eller deployment av prosjekter, men jeg lager endel andre scripts også. Tenkte nå jeg skulle vise frem ett av dem.

I denne blogposten vil du se et enkelt script som leser loggfiler fra en webapplikasjon, og presenterer en rapport med oversikt over aggregerte data fra loggene. Målet er ikke å vise noe som helst revolusjonerende eller fantastisk – jeg vil bare dele litt kode, og kanskje inspirere flere til å automatisere slike ting.

Loggfilene

Filene jeg skal rapportere på er hentet fra en ASP.NET app som bruker log4net til logging. Her er en liten eksempelfil:


2011-09-06 00:33:43,821 [1] INFO  APPLICATION STARTED
2011-09-06 00:33:45,075 [5] DEBUG Default.aspx (loading..) | 4666/acme/PROD1
2011-09-06 00:33:50,043 [6] DEBUG Report.aspx (loading..) | 4666/acme/PROD1
2011-09-06 00:33:57,680 [5] DEBUG Report.aspx (loading..) | 4666/acme/PROD1
2011-09-06 00:33:57,726 [5] INFO  Loading report "Traffic report (monthly)" | 4666/acme/PROD1
2011-09-06 01:01:07,555 [5] DEBUG Default.aspx (loading..) | 1233/foobar/PROD1
2011-09-06 01:01:41,472 [6] DEBUG Report.aspx (loading..) | 1233/foobar/PROD1
2011-09-06 01:01:57,638 [9] DEBUG Report.aspx (loading..) | 1233/foobar/PROD1
2011-09-06 01:01:57,681 [9] INFO  Loading report "Incoming traffic report (weekly)" | 1233/foobar/PROD1
2011-09-06 01:02:18,194 [6] INFO  Loading report "Keyword period statistics" | 1233/foobar/PROD1

Her er det mye nyttig informasjon jeg gjerne vil rapportere på. Hver gang en side lastes logges det en debug-linje med sidens navn, og på slutten av linjen legges det på informasjon om brukeren. Og hver gang en bruker kjører en rapport logges det en info-linje med rapportens navn.

Webapplikasjonen genererer én ny logfil hver dag. De har et fast navn, med datoen lagt på som et suffix. Dagens logg heter MyWebApp.log, loggen for i går heter MyWebApp.log20110910, osv. Skriptet som analyserere filene må kunne slå sammen data fra et vilkårlig antall logfiler.

Resultatet

Jeg ønsker en tredelt rapport basert på disse logfilene. For det første ønsker jeg å vite hvor mange ganger de ulike sidene har blitt lastet. Deretter vil jeg vite hvilke brukere som er mest aktive, og til slutt vil jeg vite hvilke rapporter som kjøres flest ganger.

Scriptet mitt skal printe ut rapporten til konsollet. Ønsker jeg å lagre rapporten kan jeg da enkelt “pipe” output til en fil i stedet. Her er output basert på filen ovenfor:

Analysing file MyWebApp.log

Page                                                    Hits
------------------------------------------------------------
Report                                                     4
Default                                                    2

User                                                    Hits
------------------------------------------------------------
foobar              PROD1  ID:1233                         5
acme                PROD1  ID:4666                         4

Report                                                  Hits
------------------------------------------------------------
Keyword period statistics                                  1
Incoming traffic report (weekly)                           1
Traffic report (monthly)                                   1

Scriptet

Jeg skal ikke si så alt for mye om koden – håper den klarer å stå på egne ben. Men jeg har delt den opp litt for å forenkle lesingen. Her er første chunk, som i hovedsak består av en main-metode som først validerer parametrene, deretter parser log-filene (linje 35) og til slutt skriver ut rapporten (37).

10 unless ARGV.length == 2
11   raise <<EOF
12 
13 
14 Usage:   mywebapp_stats.rb folder filepattern
15 
16 Example: mywebapp_stats.rb . AccountWeb.log*
17 
18 EOF
19 end
20 
21 $user_hash = {}
22 $page_hash = {}
23 $report_hash = {}
24 
25 def main
26   log_dir = ARGV[0]
27   files_pattern = ARGV[1]
28 
29   if File.directory?(log_dir)
30     Dir.chdir log_dir
31   else
32     raise "#{log_dir} is not a directory"
33   end
34 
35   Dir.glob(files_pattern).each { |f| parse_file f }
36 
37   report
38 end

Og så følger koden som parser selve logfilene. Parsingen er basert på regulære uttrykk, så det kan se litt kryptisk ut. Magien ligger i reg_count, som inkrementerer en teller i en hashtabell hvis det regulære uttrykket matcher linjen. inc_hash! gjør selve teller-inkrementeringen.

40 ### ANALYSE FILE STUFF ###############################################
41 
42 def parse_file filepath
43   puts "Analysing file #{filepath}"
44   File.open(filepath, "r") do |f|
45     while (line = f.gets)
46       reg_count(/Loading report "(.*)"/, line, $report_hash)
47       reg_count(/\| (\d+)\/(.*)\/(PROD[123])/, line, $user_hash) do |m|
48         m[0][1].ljust(20) + m[0][2] + "  ID:" + m[0][0]
49       end
50       reg_count(/ ([A-Za-z]+)\.aspx \(loading\.\.\)/, line, $page_hash)
51     end
52   end
53 end
54 
55 def reg_count pattern, line, hash, &block
56   match = line.scan pattern
57   if match.length > 0
58     key = block.call(match) if block
59     inc_hash! key || match[0][0], hash
60   end
61 end
62 
63 def inc_hash! k, h
64   n = h[k] || 0
65   h[k] = n + 1
66 end

Det eneste som gjenstår da er å skrive ut rapporten.

68 ### REPORT OUTPUT STUFF ###############################################
69 
70 def report
71   report_hash "Page", "Hits", $page_hash
72   report_hash "User", "Hits", $user_hash
73   report_hash "Report", "Hits", $report_hash
74 end
75 
76 def report_hash col_a, col_b, h
77   report_header col_a, col_b
78   h.
79     sort{|a,b| a[1] <=> b[1]}.
80     reverse.
81     each { |key, value| report_line(key, value) }
82 end
83 
84 def report_header col_a, col_b
85   puts
86   report_line col_a, col_b
87   puts "-" * 60
88 end
89 
90 def report_line a, b
91   puts a.ljust(50) + b.to_s.rjust(10)
92 end
93 
94 main # run the program

Spørsmål? Uforståelig? Elementært?

Her er noen andre eksempler på hva jeg bruker Ruby til: Script IIS Manager med IronRuby | Litt ADO.NET i IronRuby | Slette/tømme MSMQ-køer med IronRuby | En minimal HTTP-server i Ruby

Mist får en side

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!

En universell server (video)

Har du lyst til å se meg kode? Jeg har laget en code cast video jeg håper du vil like, men først litt bakgrunn…

Joe Armstrongs foredrag om Message Passing Concurrency in Erlang på QCon London i fjor forandret meg! Han viste meg en måte å trylle med kode på som inntil da hadde vært helt fremmed for meg. Etter det har Erlang, og deretter Clojure, formet nye mønstre i hjernen min, om hvordan det er mulig å skape fantastiske ting med dynamisk kode.

I presentasjonen lagde Armstrong noe han kalte for en universell server – en server som kunne bli hva som helst, bare ved at man sendte den ulike kommandoer. I min video lager jeg en lignende server i Clojure. Jeg forventer ikke at min video skal gi noen den samme kvasireligiøse a-ha-opplevelsen som Joe gav meg, men kanskje den kan gi deg noen nye ideer…

Jeg hadde veldig lyst til å bruke Daft Punk’s TRON Legacy soundtrack som bakgrunnsmusikk, men jeg turde til slutt ikke å bruke musikk jeg ikke har rettighetene til. Musikken er derfor hentet fra Mevio’s Music Alley. Skru på høytalerne!

The Universal Server from Torbjørn Marø on Vimeo.

Vil gjerne høre hva du synes!

Script IIS Manager med IronRuby

Det er ikke ofte det går en hel måned mellom hver gang jeg blogger. Men den siste tiden har jeg slitt endel med nakkesmerter, så noe har desverre måttet vike. Energien er på vei tilbake, og jeg satser på å komme igang igjen med å blogge om PingLang og DSLer snart.

Men for å starte litt forsiktig kommer det her en liten blogpost om å automatisere konfigurering av Internet Information Server ved hjelp av IronRuby.

Jeg fikk nemlig nettopp behov for å laget et script som kunne endre den fysiske stien til en website i IIS. Vi har to ulike sites, la oss kalle dem thesite.com og thesite-staging.com. Den første adressen er produksjonsadressen, den andre bruker vi til testing.

På serveren har vi så to ulike, fysiske lokasjoner for disse sitene. Stiene kan for eksempel være c:\sites\thesite-blue\ og c:\sites\thesite-green\. Til enhver tid inneholder én av disse produksjonskoden, og den andre folderen brukes til å teste neste versjon. Når den nye vesjonen er godkjent bytter vi om de fysiske stiene til thesite.com og thesite-staging.com, slik at thesite.com nå inneholder den nye versjonen.

Dette kalles blue-green deployment.

For å kunne programmere slike ting kommer IIS med en assembly som heter Microsoft.Web.Administration – du finner den under katalogen Windows\System32\inetsrv.

The different logical objects available include sites, applications, application pools, application domains, virtual directories, and worker processes. You can use the API to obtain and work with the configuration and state of these objects and to perform such actions as creating a site, starting or stopping a site, deleting an application pool, recycling an application pool, and even unloading application domains.

Du kan bruke den i C#, fra PowerShell, eller hvilket som helst språk som kan kjøre på .net-plattformen. Jeg foretrekker IronRuby til denne typen oppgaver. Her er et script (et noe enklere script enn det jeg faktisk bruker) som henter ut den virtuelle katalogen til to IIS-sites og bytter fysisk sti mellom dem:

10 require 'c:\\Windows\\System32\\inetsrv\\Microsoft.Web.Administration.dll'
11 include Microsoft::Web::Administration
12 
13 # Monkey patch ServerManager: 
14 #   add get_vdir method to get the main virtual dir
15 class ServerManager
16   def get_vdir site
17     self.
18       sites[site].
19       applications['/'].
20       virtual_directories.
21       find {|vdir| vdir.path == '/'}
22   end
23 end
24 
25 server = ServerManager.new
26 
27 a = server.get_vdir 'thesite.com'
28 b = server.get_vdir 'thesite-staging.com'
29 
30 # Swapping to variables in Ruby is easy..
31 a.physical_path, b.physical_path = b.physical_path, a.physical_path
32 
33 server.commit_changes
34 server.dispose

Vanskeligere er det ikke!

PSWinCom Ruby Gem

pswincomgemI går slapp jeg en aldri så liten før-valentinegave til alle Ruby-utviklere. Med pswincom gem’en kan man på få sekunder implementere enkel sending av SMS i et hvilket som helst Ruby-prosjekt.

Versjon 0.1.1 inneholder kun én metode, og lar deg sende en SMS med PSWinCom’s SMS Gateway – gitt at du har en konto. Den vil bli utvidet med mer funksjonalitet om det viser seg at det er interesse for det i utviklermiljøet.

Her er en kjapp demo akkompagnert av litt raggae:

Dette er den første gem’en jeg har laget og publisert. Det viste seg å være en ganske enkel og smertefri prosess, og frister til gjentagelse. Nå håper jeg bare det er noen Ruby-utviklere som har behov for å sende SMS og som kan gi meg litt tilbakemelding.

For ordens skyld, gem’en er kraftig inspirert av Clickatell Ruby API, utviklet av Luke Redpath.

For mer info om API’ene våre kan du ta en titt på wiki.pswin.com.

Ping Ring del 3: Ruby

Dette er del 2 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.

Neste språk ut er Ruby. Denne implementasjonen er logisk sett identisk med C#-varianten fra del 2. TCP-logikken er ørlite enklere (jeg trenger ingen StreamReader/Writer), og Ruby’s kodeblokker gjør tråd-logikken mere elegant å sette opp enn med C#’s lambda-uttrykk pluss et kall til start(). Men i bunn og grunn er det liten forskjell i hvor enkelt det er å løse oppgaven.

Hovedforskjellen er syntaksen. Ruby’s dynamiske typing gjør at vi unngår endel av hva jeg vil kalle støy. Syntaksen føles mere “komprimert” i Ruby, og det ser man igjen i bl.a. færre linjer kode. Min Ruby-variant “klokker seg inn” på 59 linjer – mot C# sine 90.

1 require socket
2
3 class RingServer
4   def initialize
5     @self_port = ARGV.shift
6     @other_port = ARGV.shift
7     @max_delay = ARGV.shift.to_i
8     @should_send_startup_ping = ARGV.shift == true
9     @last_ping_time = Time.now
10     puts ** Ruby Ring Server (#{@self_port})
11   end
12   def start
13     send_delayed_ping if @should_send_startup_ping
14     start_missing_ping_alert_thread
15     start_ping_listener_thread
16     Thread.list.each { |t| t.join unless t == Thread.main }
17   end
18   def send_delayed_ping
19     Thread.new do
20       sleep 1
21       begin
22         client = TCPSocket.new(127.0.0.1, @other_port)
23         client.print Ping from #{@self_port}
24       rescue
25         puts *** Failed sending ping: #{$!}
26       else
27         client.close
28       end     
29     end
30   end
31   def start_missing_ping_alert_thread
32     Thread.new do
33       while true
34         sleep 5
35         ping_delay = Time.now – @last_ping_time
36         if ping_delay > @max_delay
37           puts *** ALERT, RING BROKEN! No ping in #{ping_delay} seconds.
38           send_delayed_ping # try to wake up ring
39         end
40       end 
41     end
42   end
43   def start_ping_listener_thread
44     Thread.new do
45       listener = TCPServer.new(127.0.0.1, @self_port)
46       while session = listener.accept
47         process_incoming_ping session.gets
48         session.close
49       end
50     end
51   end
52   def process_incoming_ping message
53     @last_ping_time = Time.now
54     puts Received #{message}
55     send_delayed_ping
56   end
57 end
58
59 RingServer.new.start

Det vil være riktig av deg å påpeke at jeg ikke bruker blanke linjer på samme måte i denne koden som jeg gjorde i C#, og at det derfor er naturlig at det blir færre linjer. Men ta en titt nedenfor, hvor jeg har satt opp de to implementasjonene side om side. Her har Ruby-varianten fått seg endel linjeskift, slik at metodene som gjør det samme starter på samme linjenummer.

pingring_csharp_vs_ruby

Det er ikke tvil om hvilken versjon jeg synes er mest elegant. Men elegansen ligger altså i at C#-syntaksen har for mye støy og staffasje, ikke at det var spesielt mye enklere å implemetere i Ruby.

Jeg må derimot få påpeke at Ruby gir meg større fleksibilitet i hvordan jeg ønsker å organisere koden. Jeg valgte å bruke den samme strukturen som C#-varianten denne gangen; én klasse med seks metoder som løste helt konkrete behov. Jeg kunne derimot for eksempel ha valgt en klasse-løs implementasjon – uten å miste noe lesbarhet av betydning. Ruby egner seg etter mening veldig godt til “små skripts” som dette, hvor kravet til “innpakning” (encapsulation) ikke er så stort.

Alt i alt: Ikke en veldig stor forskjell, men Ruby trekker det lengste strådet! I neste del vil du få se programmet i et språk som har enda finere og mere kompakt syntaks, så følg med..

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.

Hva er en Monad?

yin_yang

Monad er et ord med mange betydninger; det brukes blant annet innenfor ulike filosofiske grener for å beskrive “universets essens”, eller som et slags navn på Gud. Det kjente tegnet fra kinesisk filosofi og kosmologi, som kineserne kaller T’ai-Chi, kalles også the Great Monad i Vesten, og representerer den underliggende harmonien mellom motstridende krefter i universet (mann/kvinne, yin/yang, hard/myk, sol/måne). Men ordet har også en betydning i matematikken, og den er overført til informatikk, hvor det er mest kjent som en feature i programmeringsspråket Haskell.

Windows PowerShell er også kjent under kodenavnet Monad, men meg bekjent er dette ikke relatert til betydningen i de funksjonelle språkene.

Mange betrakter monads som det mest skumle og uforståelige med funksjonelle språk, og det tar tid å skjønne hva det er for noe. Jeg har akkurat begynt å forstå det selv, og dette er forklaringen jeg skulle ønske jeg hadde sett før jeg begynte å lese de mer teoretiske beskrivelsene. Forhåpentligvis kan dette være til hjelp for andre som sliter med det samme, men jeg gir ingen garantier.

Du skal ikke se bort fra at du har brukt monader allerede

Da jeg begynte å lese meg opp på F# lærte jeg at man har monads der også, men at man kaller dem computation expressions. Og så sa læreboken at LINQ i .net også er computation expressions. Tidligere har jeg også hørt at LINQ er en form for list comprehension, som jeg lærte meg da jeg begynte å bruke Erlang, og som jeg senere har funnet igjen i flere andre språk. (List comprehension er en slags spesialsyntaks for å generere lister.) Det viser seg at list comprehensions kan kalles bruk av en list monad, og monad comprehension er en mer generell variant av list comprehensions.

Ok, du ble kanskje ikke noe klokere av dette, men saken er at alle disse tingene henger sammen på et vis. Du vil forhåpentlig vis se lyset mot slutten av blogposten!

For å skape noen nye hjernekoblinger kan vi først ta tak i litt Ruby-kode. Følgende er en banal, liten kodesnutt som deklarerer et par variabler som brukes til å beregne og skrive ut en verdi:

n = 1337
m = n * 3.1415
puts m # Skriver ut: 4200.1855

Dette er imperativ kode, slik man for eksempel finner i C og alle etterfølgerne av det språket. I Ruby har vi derimot muligheten til å gjøre koden mer funksjonell ved å gjøre følgende:

def times_pi x
  yield x * 3.1415
end

times_pi(1337) {|m| puts m} # Skriver ut: 4200.1855

Legg merke til at det ikke er noen likhetstegn i denne koden – det deklareres ingen variabler. I stedet kaller man en metode (times_pi) hvor man sender med en anonym funksjon som et av argumentene (en kodeblokk). Denne kodeblokken kalles så i funksjonen ved å bruke nøkkelordet yield. Når metodekallet er ferdig finnes det ingen variabler – ingen state!

For å bli en dyktig Ruby-utvikler må man endre tankemønsteret man bruker fra kode som i det første, imperative eksempelet til kode som det man finner i det andre eksempelet hvor man bruker kodeblokker. Men dette er også et stort steg på veien mot å lære seg funskjonell programmering. Times_pi er nemlig en høyere-ordens funksjon (den tar en funksjon som et parameter), og koden har ingen variabler / state. Vi har snudd måten vi tenker på (en slags Inversion of Control).

Hva har dette med monad å gjøre?

Jeg skal snart vise deg en monad, men ta først en titt på følgende, skrekkelige eksempel på Clojure-kode:

; BAD BAD BAD – DON’T DO THIS EVER!!!
(def pi 3.1415)  ; pi = 3.1415
(def n 1337)     ; n = 1337
(def m (* n pi)) ; m = n * pi
(println m) ; Skriver ut: 4200.1855000000005

Her har jeg skrevet imperativ kode i Clojure, noe det ikke akkurat er laget for (men det fungerer). I virkeligheten ville jeg ha gjort det slik:

(let [pi 3.1415
      n  1337
      m  (* n pi)]
  (println m)) ; Skriver ut: 4200.1855000000005

Let lar meg definere et sett med verdier som så er tilgjengelig innenfor let-kallet. Når let-kallet er ferdig opphører verdiene å eksistere. Let er faktisk en monad – i Haskell er den kjent som Identity Monad. Det let gjør er å transformere argumentene sine til en kjede med funskjonskall, og resultatet blir noe sånn som dette:

((fn [pi]
     ((fn [n]
          ((fn [m]
               (println m)) 
           (* n pi))) 
      1337)) 
 3.1415) ; Skriver ut: 4200.1855000000005

Den koden er jo nærmest uleselig, og i alle fall ekstremt vanskelig å skrive (selv for dette lille eksempelet). Og det er hele poenget: Let-monaden lar meg på en enkel måte skrive imperativ-lignende kode – noe som ser ut som statements – som så blir omformet og kjedet sammen i en pipeline av funksjonskall (ren funskjonell kode).

Så hva er en monad?

Monader er altså i prinsippet, slik jeg ser det, syntaktisk sukker som omformer kode til funksjonskall etter bestemte regler. Tenk for eksempel på LINQ, som er en spørre-syntaks som under panseret blir omformet til funksjonskallene Select(), Where(), etc. Det er en formell teori knyttet til monad som snakker om bind og return, og forteller hvordan denne omformingen skal fungere. Dette er derimot ikke så viktig før man skal lage sine egne monads.., og jeg er ikke der enda. Det har derimot ikke hindret meg fra å bruke monads lenge uten å vite at det var det jeg gjorde i det hele tatt.

Monads er dermed ikke så skummelt lenger. List comprehensions og LINQ hjelper meg til å unngå løkker, og til å skrive bedre og mer deklerativ kode. Og let-monaden er helt uvurderlig i Clojure.

Andre monader

En annen, velkjent monad kalles Maybe-monad. Den fungerer i prinsippet slik at hvis én av handlingene i sekvensen/pipelinen returnerer null/ingenting så vil hele monaden returnerer det samme. På den måten kan monader forenkle kode hvor man normalt må sjekke for null. Det samme prinsippet kan brukes for å forenkle unntakshåndtering.

Ellers er vel ingen omtale av monad komplett uten å nevne I/O (input/output). Haskell bruker monads til dette, og det garanterer at I/O-handlinger (som jo har sideeffekter) kun utføres én gang, og i riktig rekkefølge. Det er altså ikke riktig at Haskell er et språk uten sideeffekter, slik jeg har blitt fortalt. Men språket krever at handlinger som har sideeffekter utføres i spesielle strukturer (altså monads) som håndterer sideeffektene på riktig måte. Dermed står man friere til å betrakte koden som “rent funksjonell”, og man kan utføre andre deler av koden i “valgfrie” rekkefølger, benytte lazy evaluering effektivt etc.

Vel, det var mine betraktninger rundt monad, og en forklaring for dem som bare har litt erfaring med funksjonelle språk. Det er kun kort tid siden jeg følte at jeg begynte å forstå konseptet, så om du betrakter noe av dette som feil får du bare si fra. Min kunnskap er selvsagt langt fra komplett, så jeg vil gjerne høre andre synspunkter.


Torbjørn: La oss anta to ulike definisjoner av Template Method pattern - de to ytterpunkte...

Lars-Petter: Hei igjen. Siden du inviterer til å ta diskusjonen i bloggen, og har tatt deg t...

Torbjørn: Lars-Petter: Det er én måte å se det på. Det er absolutt fortsatt Template M...

Lars-Petter: Hei. Har du ikke i prinsippet her gått over fra Template Method (arv) til Strat...

Christian Abildsø: I alle fall i C#, så føles dette som kode som blir mer fleksibel men vanskelig...

Torbjørn: Hei Henrik, og takk for tilbudet. Ble oppmerksom på Rasberry Pi for under en uk...

Henrik Sandaker Palm: Ang. større hobby prosjekt. Du er som er en slik rakker på programmering har j...

Øivind Nilsen: Slutt å bruke mobilnummeret mitt som eksempel !...

Bjørn Einar Bjartnes: Jeg har også latt meg fascinere av Clojure, uten at jeg har kommet så veldig l...

Bjørn Einar Bjartnes: Sweet :) Jeg tror egentlig jeg liker det som det er, med musikk. Litt av utford...

 Hold deg oppdatert

Søk i bloggen

Ferske innlegg

  • Template Method del 4: Multippel arv
  • Template Method Intermesso
  • Template Method del 3: Bare funksjoner
  • Template Method del 2: På vei mot funksjonell programmering
  • Kategorier

  • .net ninja (37)
  • Bøker (17)
  • Diverse prosjekter (35)
  • DSL (10)
  • Erlang (10)
  • F# (5)
  • Hardware (1)
  • Jobb (77)
  • Julekalender (51)
  • kjempekjekt.com (23)
  • LISP/Clojure (33)
  • NNUG / community (60)
  • O/RM & databaser (10)
  • Off topic (116)
  • OO-design/clean code (30)
  • Podcasts (14)
  • Polyglot (77)
  • Ruby (27)
  • Silverlight / RIA (3)
  • Software/verktøy (20)
  • Softwareutvikling (20)
  • Testing / TDD (30)
  • the contiki strip (13)
  • User experience (3)
  • WCF (3)
  • Webutvikling (32)
  • WPF (9)
  • WTF (12)
  • Last ned Wallpaper

    Programmeringsbloggens tøffe skrivebordsbakgrunn med snippets fra ulike språk laster du ned her!

    Abonner via epost

    Om du vil kan du få alle nye blogposter tilsendt til din epost. Abonner nå, det er kjempeenkelt!

    Meta