filtrer, projiser, aggreger

Det siste året har jeg knyttet intim bekjentskap til bruk av såkalte higher order functions; først da jeg virkelig tok i bruk LINQ og lambda i C# 3.0, deretter da jeg lærte meg Ruby, og til sist nå når jeg lærer meg å bruke språk som Erlang og F# i den funksjonelle paradigmen. Jeg har lært om Lambda calculus, og hva en closure er for noe – ting jeg nå mener alle utviklere burde ha et forhold til.

For å gi deg, kjære leser, et bedre innblikk i noe av dette, vil jeg snakke litt om noen av disse funksjonene av “høyere orden”. Higher order function betyr i bunn og grunn bare en funksjon som tar som innput en annen funskjon som spesifiserer en del av hva den skal utføre.

Filtrer

Filter er en av de mest vanlige, høyere orden funskjonene som brukes på lister eller andre datastrukturer. Gitt en liste og et predikat så brukes filter-funksjonen til å returnere de elementene i listen som tilfredstiller predikatet. Jeg sier “filter” fordi det er det funksjonen heter i de fleste programmeringsspråkene. I C# heter derimot filter-funksjonen for IEnumerable<T> Where, og den kan for eksempel brukes slik:

var aList = new []{1, 2, 3, 5, 8, 13, 21, 33, 54};
var evenNumbers = aList.Where(n => n % 2 == 0);
// array contains: 2, 8, 54

Filter-funksjonen for Enumerations i Ruby heter select (evt. find_all). Her er samme eksempel i Ruby:

1 $a_list = [1, 2, 3, 5, 8, 13, 21, 33, 54]
2 $even_numbers = $a_list.select {|n| n % 2 == 0}
3 # array contains: 2, 8, 54

Projiser

Den neste av de mest brukte, høyere orden funksjonene er Map. Denne funksjonen tar en liste eller lignende, samt en funksjon for å omforme (“mappe”) elementene i listen til en ny type elementer. Dette kaller vi også en projisering (projection). C#-varianten av map heter Select (og er altså ikke det samme som select i Ruby). Ønsker jeg for eksempel å konvertere listen med tall om til en liste med strenger kan jeg gjøre det slik:

var evenNumbersAsString = evenNumbers.Select(n => n.ToString());

I de fleste andre språk heter funksjonen map, men i Ruby har man også et alias som heter collect.

Aggreger

Filter og map er fine funksjoner som er anvendelige og lette å forstå og å huske på. En som ikke er fullt så mye brukt, blant utviklere innenfor den imperative paradigmen i alle fall, er Fold. For å forvirre oss fremstår også denne funksjonen med ulike navn i de ulike språkene – som for eksempel Reduce, Accumulate, Compress og Inject. Og i .NET heter den Aggregate:

Console.Write(evenNumbersAsString.Aggregate(
    "", (accumulator, next) => accumulator + next + " "));
// prints "2 8 54 "

Ser du hvordan den virker? I motsetning til de andre funksjonene som returnerer lister, så returnerer Aggregate én verdi. En verdi som er bygget opp ved å kalle det spesifiserte lambda-uttrykket for hvert element i listen. Accumulator er både output og input til lamdaen. For det første elementet vil accumulator ha verdien som ble spesifisert som den første parameteren til Aggregate, altså en tom streng. Når funksjonen kalles igjen for det andre elementet vil accumulator ha verdien som ble returnert fra den første. Og så fortsetter det til listen er tom, og accumulator returneres.

Her er nesten samme eksempel i Ruby, hvor fold heter inject (eller reduce om du ønsker å bruke det):

5 $foo = $even_numbers.inject() {|acc,next| #{acc}#{next.to_s}, }
6 puts $foo[0..-3] # outputs “2, 8, 54″

Har du aldri brukt en slik funksjon ser dette sikkert ganske gresk ut. Den er på kanten til å være uleselig, og hvis du jobber i et team som ikke er så godt kjent med fold/aggregate så bør du kanskje være forsiktig med å bruke den. Men jeg lover deg at om du venner deg til denne funksjonen så vil du i mange tilfeller kunne produsere mere kompakt og elegant kode.

Oppsummering i F#

Her er et oppsummerende eksempel i F#. Jeg oppretter en liste, og bruker pipelining til å sende listen gjennom filter, map (projiser) og fold (aggreger), for til slutt å skrive ut den resulterende strengen.

open System
 
let aList = [1; 2; 3; 5; 8; 13; 21; 33; 54]
 
aList |> List.filter (fun n -> n % 2 = 0)
      |> List.map    (fun n -> n.ToString())
      |> List.fold   (fun acc n -> acc + n + " ") String.Empty
      |> Console.WriteLine

Kategorier: F#, Polyglot, Ruby.
RSS feed for kommentarene. Tilbaketråkk.

6 kommentarer til “filtrer, projiser, aggreger”

  1. Ole Gunnar Borstad Says:

    Ryddig framstilling , gode eksempler, enkle forklaringer. Bra innhold som alltid på denne bloggen!

    Det er mange som ikke vet om mulighetene med høyere ordens funksjoner og f.eks LINQ. Temaet kan virke veldig tungt hvis det framstilles for akademisk, så det er godt å se en lettspiselig forklaring på temaet!

  2. Ameth Says:

    Currying og nå Higher Order Functions… Jeg synes du går i riktig retning ^_^

    Og som vanlig:
    putStrLn . foldl1 ((++).(++” “)) . map show . filter even $ aList
    («foldl1 ((++).(++” “))» kalles også «unwords», bortsett fra at førstnevnte ikke tåler en tom liste.)

  3. Torbjørn Says:

    Takk for hyggelige kommentarer! Og Armeth,jeg må innrømme at jeg klassifiserer dine Haskell-eksempler blant noe av den mest uleselige koden jeg har sett. Ser ut som et forsøk på å være kryptisk. Selv Lisp, med alle parantesene, ser bedre ut etter å ha fått en fem minutters introduksjon ;)

  4. Ameth Says:

    Hmm? Det er jo akkurat samme kode som din, bare baklengs. Hvis du tenker på «((++).(++” “))» så er en «pointfree» versjon av «(\acc n -> acc ++ ” ” ++ n)» og “unwords” er som sagt et mer leselig alternativ, men da ser du ikke at det er en fold involvert.

    Er dette bedre?
    (|>) = flip ($)
    aList |> filter even |> map show |> unwords |> putStrLn

  5. Torbjørn Says:

    Jepp, mye bedre! ;)

    Selvfølgelig kan det tenkes at jeg er utsatt for å tolke leseligheten som dårlig fordi jeg ikke er vandt til syntaksen. Men jeg har tatt meg selv i å lure på om man ikke ofrer litt vel mye lesbarhet når man skriver så konsis kode som jeg ofte ser spesielt fra funksjonsbaserte språk. “Pointfree” er ett eksempel på noe jeg vil være forsiktig med.

    Jeg antar at pipelining ikke er så vanlig å bruke i Haskell, og kanskje ikke i andre FP-språk heller, men for en med OO-bakgrunn blir det lettere å forstå. F#-koden min i oppsummeringen ville i praksis vårt helt lik i C# om jeg brukte Linq og method chaining.

    I objektorientert programmering har vi blitt mer og mer opptatt av lesbar kode, og bryter f.eks. gjerne opp i mange små metoder kun for å oppnå beskrivende navn / selvdokumenterende kode. Kunsten er å balansere mengde kode med hvor mye hjernekapasitet som må til for å forstå den! Utviklere bruker mye mer tid på å lese kode enn å skrive den..

  6. Ameth Says:

    Joda, pipelining brukes mye, i den grad det kan kalles det. Hele do-notasjonen er syntaktisk sukker for pipelining av monader ((do {x >= \x -> putStrLn x) == (getLine >>= putStrLn)), men for funksjoner er det som oftest baklengs som i det første eksempelet mitt. (|>) er som du ser bare en annen funksjon med dens argumenter byttet om (flip f a b = f b a) (og (|>) er også definert i «pointfree»…)

Skriv en kommentar

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


Einar W. Høst: Det er jo læringen som gjør det morsomt! Se også http://norvig.com/21-days...

Pagliacci: OBS! tl;wr. Det er vel akuratt det jeg sliter med med min læring innenfor pr...

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 !...

Mulig relaterte linker

 Hold deg oppdatert

Søk i bloggen

Ferske innlegg

  • En historie om programmering
  • Template Method del 4: Multippel arv
  • Template Method Intermesso
  • Template Method del 3: Bare funksjoner
  • 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 (21)
  • 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