O'Caml

Från Unix.se, den fria unixresursen.

O'Caml (eller Objective Caml) är ett strikt, statiskt, objektorienterat, funktionellt språk. Det kan kompileras till maskinkod och sägs då vara nästan lika snabbt som GCC-genererad kod (åtminstone i den här (http://shootout.alioth.debian.org/) jämförelsen) eller liksom Java kompileras till bytekod.

Innehåll

Kommentarer

Kommentarer omsluts i O'Caml av (* respektive *) och kan, liksom C:s motsvarighet, sträcka sig över flera rader.

(* Det här är en kommentar som 
   sträcker sig över flera rader. *)

Värden

Allmänt

O'Caml har en interaktiv tolk som är utmärkt att använda sig utav medan man exempelvis följer en introduktion som den här. Starta denna med kommandot ocaml (eller så använder du exempelvis tuareg-mode (http://www-rocq.inria.fr/~acohen/tuareg/) i Emacs, vilket jag tycker är att föredra, och kör tolken där istället. Det medför att du kan utvärdera uttryck som du skriver i en egen buffer med C-M-x vilket är mycket praktiskt.). Du borde mötas av ett meddelande i stil med:

$ ocaml
       Objective Caml version 3.08.1
#

Efter #-tecknet skriver du uttryck (eng. "expressions") som utvärderas. O'Caml svarar med att skriva ut värdet som uttrycket returnerar.

# 3 + 4;;
- : int = 7

Till skillnad från en del andra språk (exempelvis C/C++ och Java) finns det inga uttryck som inte returnerar ett värde. Detta beror på att O'Caml är ett funktionellt språk, och således är uppbyggt enbart av funktioner. Eftersom funktioner alltid returnerar ett värde måste även allt i O'Caml göra det, och inget får ha sidoeffekter (vilket är en sanning med modifikation -- ett programmeringsspråk helt utan sidoeffekter skulle inte ens få skriva ut saker på skärmen, och därmed vara tämligen värdelöst). Till saken hör dessutom att O'Caml även i viss utsträckning kan användas som ett imperativt språk; vi återkommer till detta senare.

O'Caml har följande grundläggande typer:

Typ            Omfattning

int            31-bitar signerat heltal
float          Dubbelprecisionsflyttal, ekvivalent med C:s double
bool           Booleanskt värde; antingen true eller false
char           Ett 8-bitarstecken
string         En sträng
unit           Skrivs som (), motsvarar ungefär C:s void

Konstanter definieras med nyckelordet let:

# let foo = "Hello World!";;
val foo : string = "Hello World!"

De flesta värdena i O'Caml är konstanter, eller "immutable" som det heter på engelska. Arrayer och strängar kan dock ändras på plats. (Liksom referenser, vilka vi kommer in närmare på senare.)

Lokala konstanter kan defineras på formen let <namn> = <värde> in <uttryck> ;; och flera definitioner läggs samman med and:

# let foo = "hej" and bar = "världen" in
        print_string (foo ^ bar) ;;
hejvärlden- : unit = ()

OK, nu var det kanske lite mycket på en gång! Läs vidare om funktioner för att förstå vad som hände här, och titta för tillfället enbart på de lokala konstanterna i exemplet. Dessa är bara definierade i det efterföljande uttrycket och överskrider tidigare definierade konstanter med samma namn.

Egna varianter

Varianter definieras på formen type <typnamn> = <variant1> [of <typ>] | <variant2> [of <typ>] | ...:

# type foo = Int of int | Pair of int * int | Bar of string ;;
type foo = Int of int | Pair of int * int | Bar of string

För att faktiskt göra något med dessa använder man följande:

# Pair (4, 10) ;;
- : foo = Pair (4, 10)

Som ni ser är Pair av typen foo, precis som vi definierade.

Om vi är ute efter motsvarigheten till C:s enum kan vi utesluta of i definitionen, liksom följande:

# type pizzas = Napolitana | Margarita | None ;;

Rekursion

Typer kan också utnyttja rekursion, vilket bland annat används när man arbetar med binärträd.

# type binary_tree = Leaf of int | Tree of binary_tree * binary_tree ;;

Exempel på enkla binärträd:

Leaf 4
Tree (Leaf 4, Tree (Leaf 5, Leaf 3))

Referenser

För att O'Caml ska stödja imperativ programmering måste något i stil med variabler införas - i O'Caml kallas dessa för referenser.

# let foo = ref 1 ;;
val foo : int ref = {contents = 1}

För att komma åt värdet (dvs. heltalet 1) som finns i foo, sätter man ett utropstecken ("!") framför variabelnamnet:

# !foo ;;
- : int = 1

Referenser ges nya värden med operatorn ":=":

# foo := 2 ;;
- : unit = ()
# !foo ;;
- : int = 2

Funktioner

Anropa

Funktioner anropas på formen <funktion> <arg1> <arg2> ... <argn>:

# min_funktion 2 3;;

Här anropas funktionen min_funktion med argumenten 2 och 3.

Som vi sa tidigare är allt funktioner i O'Caml; men ibland kan dessa förekomma gömda under syntaktiskt socker. Exempelvis funktionen "+", som tar emot två heltal och returnerar summan av dessa, kan "avsockras" med hjälp av parenteser:

# (+) 3 4;;
- : int = 7

(Notera att "+" endast fungerar för heltal; för flyttal används "+.".)

Definiera

För att definiera en egen funktion används, liksom med konstanter och lokala variabler, let:

# let min_funktion m n =
        m + n + 10;;
val min_funktion : int -> int -> int = <fun>

Funktionsdefinitionen avslutas liksom alla andra uttryck med dubbla semikolon. O'Caml är starkt statiskt typat, men kan nästan alltid själv se vilken typ argumenten till en funktion måste ha: som vi ser spottar O'Caml ut lite text efter vår förra definition. Denna visar att O'Caml själv har förstått vilket typ argumenten till min_funktion ska ha (vilket den kan se eftersom funktionen "+" tar emot två heltal, och således måste argumenten till min_funktion också vara av denna typ). Den sista raden betyder att funktionen tar emot två heltal (int), returnerar ett heltal och är en funktion (= <fun>).

Om funktionen istället skulle definieras som:

# let min_funktion m n = m +. n +. 10.0;;
val min_funktion : float -> float -> float = <fun>

Ser ni att O'Caml automatiskt känner att argumenten måste vara av typen float.

Ni märker säkert att det inte behövs ett return, eller dylikt, i slutet av funktionen. Detta beror på att O'Caml alltid returnerar värdet av det sista uttrycket i en funktion.

Rekursion

Till skillnad från många andra språk är en funktion inte som standard definierad i sin egen kropp. O'Caml har dock ett nyckelord för att det ska vara så; rec, som används på följande sätt:

# let rec range a b =
    if a > b then []
    else a :: range (a+1) b
  ;;

Notera att range anropar sig själv. Rekursion är ett kraftfullt verktyg för att lösa många problem, och används traditionellt flitigt i funktionella språk.

Polymorfism

Polymorfism är ungefär som "templates" i C++ eller Java, och gör enkelt uttryckt att en funktion kan ta emot vilket värde som helst.

# let foo n = 3;;
val foo : 'a -> int = <fun>

Vilket värde jag än matar in i funktionen foo, kommer den att returnera heltalet 3. Som signaturen visar betecknas denna egenskap med ett 'a som typ på värdet n. Det här exemplet är givetvis föga intressant och kanske inte visar exakt varför polymorfism är så användbart, men vi ska återkomma till det lite senare.

Kontrollstrukturer

Mönstermatchning

O'Caml har ett mycket praktiskt sätt att matcha datastrukturer och dess data. Säg att vi vill skriva ett program som kan räkna plus och minus, då skulle vi först kunna definiera följande varianter:

type expr =
  | Plus of expr * expr
  | Minus of expr * expr
  | Val of float

Och således skriva uttryck som ska beräknas som exempelvis:

Plus (Val (1.0), Minus (Val(3.0), Val(1.2)))

Nästa steg är att göra en funktion som på något sätt räknar ut slutresultatet, och det är här match kommer till användning.

let rec compute expr =
  match expr with
    Plus (e1, e2) -> (compute e1) +. (compute e2)
  | Minus (e1, e2) -> (compute e1) -. (compute e2)
  | Val v -> v
  | _ -> failwith ("Borde inte kunna komma hit!")
;;

Funktionen tar emot en variabel av typen expr (som vi själva definierade ovan), och sedan kollar den rekursivt vilken typ expr är och när (om) den matchar något av Plus, Minus eller Val så returnerar den resultatet. Notera den sista matchningen, "_", som är ett "wildcard" - dvs. matchar allt som inte redan har matchats. I den här funktionen borde den aldrig kunna nås, och därför ska programmet avslutas med ett felmeddelande (failwith) om så sker. Det är god sed att alltid ha med denna sista matchning, men om det finns risk för att expr inte matchas korrekt klagar O'Caml automatiskt på det och ger exempel på värden som inte matchas.

Försök att förstå funktionen innan du går vidare i den här texten!

Match-uttryck definieras alltså på formen:

match <objekt> with
  <mönster> -> <resultat>
| <mönster> -> <resultat>
  ...

Nu ska vi bara testa om vår funktion compute fungerar som den ska:

# let foo =  Plus (Val (1.0), Minus (Val(3.0), Val(1.2))) ;;
  val foo : expr = Plus (Val 1., Minus (Val 3., Val 1.2))
# compute foo ;;
- : float = 2.8

Vilket faktiskt verkar stämma! Hurra! \o/

If och Else

Liksom de flesta andra Turingkompletta programspråk inkluderar O'Caml if och else. Dessa används på formen if <booleanskt uttryck> then <uttryck> else <uttryck>. Följande exempel returnerar det största talet av två angivna:

# let max a b =
        if a > b then a else b
  ;;
val max : 'a -> 'a -> 'a = <fun>

Som ni märker blir funktionen polymorfisk eftersom > är det.

Externa länkar

Personliga verktyg