ML (γλώσσα προγραμματισμού)

Η ML είναι μια συναρτησιακή γλώσσα προγραμματισμού γενικής χρήσης, που αναπτύχθηκε από τον Ρόμπιν Μίλνερ και άλλους στο τέλος της δεκαετίας του 1970 στο πανεπιστήμιο του Εδιμβούργου. Ξεκίνησε ως μέτα-γλώσσα (εξού και το όνομα Meta-Language) για διαδραστικές αποδείξεις στο σύστημα Edinburgh LCF (τα αρχικά για "Logic for Computable Functions" - λογική για υπολογίσιμες συναρτήσεις) και εξελίχθηκε σε γενικής χρήσης γλώσσα προγραμματισμού για να καλύψει τις ανάγκες αυτής της εφαρμογής.

Τα συναρτησιακά στοιχεία της γλώσσας είναι εμπνευσμένα από την ISWIM και την GEDANKEN, αλλά διαφέρει στον χειρισμό των τύπων, ενώ άλλα στοιχεία της γλώσσας είναι εμπνευσμένα από τη Lisp and την POP2. Το συγκεκριμένο σύστημα αποδείξεων του LCF ήταν το PPLambda, που είναι συνδυασμός του πρωτοβάθμιου προτασιακού λογισμού (first-order predicate calculus) και του πολυμορφικού λογισμού λάμδα με απλούς τύπους (simply-typed polymorphic lambda-calculus). Αλλά, σύμφωνα με τον Ρόμπιν Μίλνερ, σχεδόν οποιδήποτε επαγωγικό σύστημα θα είχε οδηγήσει στις ίδιες βασικές αρχές της γλώσσας.

Η ML είναι γνωστή για τη χρήση του αλγόριθμου Χίντλεϋ-Μίλνερ για την εξαγωγή τύπων (type inference), που μπορεί να συνάγει αυτόματα τους τύπους των περισσοτέρων εκφράσεων της γλώσσας, χωρίς να χρειάζεται σαφείς προσδιορισμούς τύπων από τον προγραμματιστή. Είναι μία από τις λίγες γλώσσες προγραμματισμού με αυστηρή απόδειξη ότι όλα τα προγράμματα που περνάνε τον έλεγχο τύπων, δεν έχουν σφάλματα κατά την εκτέλεση.

 Περιγραφή 
Η ML συχνά αναφέρεται ως ακάθαρτη συναρτησιακή γλώσσα, επειδή επιτρέπει παρενέργειες, και επομένως διαδικαστικό προγραμματισμό, σε αντίθεση με αμιγώς συναρτησιακές γλώσσες όπως η Haskell. Για αυτό το λόγο λέγεται και γλώσσα προγραμματισμού μικτού προτύπου.

Χαρακτηριστικά της ML περιλαμβάνουν την στρατηγική υπολογισμού της κλήσης κατά τιμή (call-by-value), συναρτήσεις πρώτης τάξης, αυτόματη διαχείριση μνήμης με συλλογή απορριμάτων, παραμετρικό πολυμορφισμό, στατικούς τύπους, εξαγωγή τύπων (type inference), αλγεβρικούς τύπους δεδομένων (algebraic datatypes), ταίριασμα προτύπων (pattern matching) και εξαιρέσεις. Παραδείγματα αυτών των χαρακτηριστικών δίνονται παρακάτω.

Αντίθετα με τη Haskell, η ML χρησιμοποιεί πρόθυμη αποτίμηση (eager evaluation), που σημαίνει ότι όλες οι υπο-εκφράσεις μιας έκφρασης αποτιμώνται. Αποτέλεσμα είναι ότι δεν μπορούν να χρησιμοποιηθούν άπειρες λίστες ως έχει. Παρόλα αυτά, η οκνηρή αποτίμηση (lazy evaluation) και επομένως και οι άπειρες δομές, μπορούν να προσομοιωθούν με τη χρήση ανώνυμων συναρτήσεων.

Σήμερα υπάρχουν αρκετές γλώσσες στην οικογένεια της ML. Οι δύο κύριες διάλεκτοι είναι η Standard ML και η Caml, αλλά υπάρχουν και άλλες, όπως η F#, η Lazy ML, η οποία υποστηρίζει οκνηρή αποτίμηση και η CakeML, η οποία είναι ένα υποσύνολο της ML με τυπική σημασιολογία επιβεβαιωμένη στο HOL. Ιδέες από την ML έχουν επηρεάσει πολυάριθμες άλλες γλώσσες όπως τη Haskell, τη Cyclone και τη Nemerle.

Τα δυνατά σημεία της ML συγκεντρώνονται συνήθως στο σχεδιασμό και χειρισμό γλωσσών (μεταγλωττιστές, αναλυτές, αποδείκτες θεωρημάτων όπως το HOL και το Isabelle, κλπ), και γι'αυτό έχει χρησιμοποιηθεί αρκετά στην έρευνα γλωσσών προγραμματισμού. Ως συναρτησιακή γλώσσα επιτρέπει την εύκολη υλοποίηση διαχρονικών δομών δεδομένων. Έχει χρησιμοποιηθεί επίσης στη βιοπληροφορική, σε οικονομικά συστήματα, και άλλες εφαρμογές, αλλά εκεί πιο διαδεδομένες είναι παραλλαγές της γλώσσας όπως η OCaml.

 Παραδείγματα χαρακτηριστικών γλώσσας 

 Εξαγωγή τύπων 

Η ML επιτρέπει την αυτόματη εξαγωγή τύπων. Για παράδειγμα, στην παρακάτω συνάρτηση οι δηλωτές (type specifiers) είναι περιττοί:
fun increment (n:int) :int = n + 1

Η συνάρτηση μπορεί να γραφτεί ως εξής:
fun increment n = n + 1
και η γλώσσα θα εξαγάγει τον τύπο της μεταβλητής  ως ακέραιο και της συνάρτησης increment ως συνάρτηση από ακεραίους σε ακεραίους:
val increment = fn : int -> int

 Αναδρομή 

Η ML υποστηρίζει και ενθαρρύνει την χρήση αναδρομικών συναρτήσεων (και αναδρομικών τύπων). Για παράδειγμα η παρακάτω συνάρτηση υπολογίζει το  παραγοντικό.

fun factorial n = 
  if n = 0 then 1
  else n * factorial (n-1);;

factorial 4;; (* Υπολογίζει 4 * 3 * 2 * 1 = 24 *)
 > val it = 24 : int

Επίσης υποστηρίζει αμοιβαία αναδρομή (mutual recursion). Για παράδειγμα, οι παρακάτω συναρτήσεις ελέγχουν (με μη αποδοτικό τρόπο) αν ένας μη-αρνητικός ακέραιος είναι ζυγός ή περιττός, καλώντας η μία την άλλη:
fun is_odd n = 
  if n = 0 then false
  else is_even (n-1)
and is_even n = 
  if n = 0 then true
  else is_odd (n-1)

 > val is_odd = fn : int -> bool
 > val is_even = fn : int -> bool

 Συναρτήσεις ως τιμές 

Στην ML οι συναρτήσεις είναι τιμές, που σημαίνει ότι μπορούν να δοθούν ως όρισμα μίας συνάρτησης ή να επιστραφούν ως αποτέλεσμα μίας συνάρτησης. Μία συνάρτηση με όρισμα ή αποτέλεσμα μία άλλη συνάρτηση, λέγεται συνάρτηση ανωτέρου βαθμού.

 Συνάρτηση ως όρισμα 

Για παράδειγμα, η παρακάτω συνάρτηση παίρνει ως όρισμα μία οποιαδήποτε συνάρτηση  και επιστρέφει το άθροισμα :
fun sum_three f = (f 1) + (f 2) + (f 3)
Άρα, όταν την καλέσουμε για  η απάντηση είναι :
fun g x = 2 * x + 5;;
sum_three g;;

 > val g = fn : int -> int
 > val it = 27 : int

 Συνάρτηση ως αποτέλεσμα 

Για παράδειγμα, η παρακάτω συνάρτηση παίρνει ως όρισμα  και επιστρέφει την γραμμική συνάρτηση της μορφής :
fun linear_fn (a, b) = 
  let fun new_fn x = a * x + b in
     new_fn
  end;;

(* Παρατηρήστε ότι ο τύπος της συνάρτησης επιβεβαιώνει
   αυτό που θέλουμε όρισμα: int * int (δηλαδή (a,b)) και
   αποτέλεσμα: int -> int (δηλαδή μία συνάρτηση). *)
 > val linear_fn = fn : int * int -> int -> int
Επομένως, η συνάρτηση , μπορεί να οριστεί ως εξής:
val threeXplusOne = linear_fn (3, 1);;
threeXplusOne 10;;
 > val threeXplusOne = fn : int -> int
 > val it = 31 : int
Καθώς η ML χρησιμοποιεί currying (δηλαδή όλες οι συναρτήσεις μετατρέπονται ώστε να έχουν ένα όρισμα), η linear_fn μπορεί να γραφτεί πιο σύντομα ως:
fun linear_fn (a, b) x = a * x + b;;

 Ανώνυμες συναρτήσεις 

Οι συναρτήσεις μπορούν να οριστούν και ως ανώνυμες. Δηλαδή στο παραπάνω παράδειγμα, στην κλήση στο sum_three:
fun g x = 2 * x + 5;;
sum_three g;;
θα μπορούσε να χρησιμοποιηθεί μία ανώνυμη στην θέση της , ως εξής:
sum_three (fn x => 2 * x + 5)
Με αυτόν τον τρόπο αποφεύγεται η ονομασία απλών συναρτήσεων και μειώνονται οι γραμμές κώδικα. 

 Παραμετρικός πολυμορφισμός 

Η ML επιτρέπει τον παραμετρικό πολυμορφισμό, δηλαδή τον ορισμό γενικών συναρτήσεων και γενικών τύπων, που δουλεύουν για όλους τους δυνατούς τύπους της παραμέτρου.

 Παράδειγμα 1ο: Απλές συναρτήσεις 

Η πιο απλή τέτοια συνάρτηση είναι η ταυτοτική συνάρτηση
fun id x = x
η οποία έχει τύπο 
val id = fn : 'a -> 'a
To 'a είναι η παράμετρος και ο τύπος 'a -> 'a υποδηλώνει ότι συνάρτηση παίρνει ένα στοιχείο οποιοδήποτε τύπου και επιστρέφει ένα στοιχείο του ίδιου τύπου (στην συγκεκριμένη περίπτωση και το ίδιο στοιχείο). Αυτό μας επιτρέπει να γράψουμε:
id 3;;
 > val it = 3 : int
id 3.0;;
 > val it = 3.0 : real
id "Hello";;
 > val it = "Hello" : string

Μία λίγο πιο σύνθετη συνάρτηση είναι αυτή που αντιστρέφει τις τιμές ενός ζεύγους, π.χ. για είσοδο (10, "hello") επιστρέφει ("hello", 10). Αυτή μπορεί να οριστεί ως εξής:
fun swap (x, y) = (y, x);;
 > val swap = fn : 'a * 'b -> 'b * 'a
Ο τύπος 'a * 'b -> 'b * 'a σημαίνει ότι η συνάρτηση παίρνει ως όρισμα ένα ζεύγος τύπου 'a * 'b (π.χ. για το (10, "hello"), ο τύπος είναι string * int) και επιστρέφει ένα ζεύγος τύπου 'b * 'a (το ("hello", 10) με τύπο int * string).

 Παράδειγμα 2ο: Map 

Μία πιο σύνθετη συνάρτηση που χρησιμοποιείται αρκετά συχνά είναι η map. Αυτή επιτρέπει την εφαρμογή μία συνάρτησης σε κάθε στοιχείο μίας λίστας. Για παράδειγμα, 
(* Προσθέτει +10 σε κάθε αριθμό της λίστας *)
map (fn x => x + 10) [1, 6, 2, 7, 8];;
 > val it = [11,16,12,17,18] : int list
(* Επισημαίνει ποιοι από τους αριθμούς είναι ζυγοί *)
map (fn x => x mod 2 = 0) [1, 5, 2, 7, 8];;
 > val it = [false,false,true,false,true] : bool list
map (fn x => if x then "Even" else "Odd") [false,false,true,false,true];;
 > val it = ["Odd","Odd","Even","Odd","Even"] : string list

Η συνάρτηση map μπορεί να οριστεί ως εξής:
fun map f [] = []
|   map f (x::xs) = (f x)::map f xs;;
 > val map = fn : ('a -> 'b) -> 'a list -> 'b list
Ο τύπος της δηλώνει ότι δέχεται μία συνάρτηση τύπου 'a -> 'b (για παράδειγμα fn x => x mod 2 = 0 που ελέγχει αν ο  είναι ζυγός) και μία λίστα τύπου 'a list (π.χ. μία λίστα ακεραίων) και επιστρέφει μία λίστα  τύπου 'b list (π.χ. μία λίστα από booleans).

Παρόμοιες συναρτήσεις είναι η filter, exists, foldr.

 Παράδειγμα 3ο: Σύνθεση συναρτήσεων 

Μπορούμε να ορίσουμε μία συνάρτηση που συνθέτει δύο συναρτήσεις ως εξής:
fun compose f g x = f ( g x )
 > val compose = fn : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b

Άρα για την σύνθεση της  και της  (που έχει ως αποτέλεσμα την ), γράφουμε: 
val h = compose (fn x => 2 * x + 1) (fn x => x * x + 2)
h 10 (* Επιστρέφει 2 * 10^2 + 5 *)

Άλλη μία εφαρμογή της compose είναι η συγχώνευση δύο map. Στο προηγούμενο παράδειγμα τα δύο τελευταία map μπορούν να αντικατασταθούν με ένα:
map (compose (fn x => if x then "Even" else "Odd") (fn x => x mod 2 = 0)) [1, 5, 2, 7, 8]
 > val it = ["Odd","Odd","Even","Odd","Even"] : string list

 Αλγεβρικός τύπος δεδομένων 

Η ML υποστηρίζει τον ορισμό καινούργιων αλγεβρικών τύπων δεδομένων.

Στην πιο απλή περίπτωση, αυτοί μπορεί να είναι απαριθμητικοί τύποι, π.χ. οχήματα:
datatype Vehicle = Bicycle | Car | Truck;;
fun vehicle_to_str Bicycle = "bicycle"
|   vehicle_to_str Car = "car"
|   vehicle_to_str Truck = "truck";;
 > val vehicle_to_str = fn : Vehicle -> string
vehicle_to_str Truck;;
 > val it = "truck" : string

Στην λίγο πιο σύνθετη μορφή, κάποιοι από τους τύπους μπορούν να έχουν παραπάνω πληροφορίες. Άμα θέλαμε για τα οχήματα να ξέρουμε και πόσες ρόδες έχουν, τότε θα αλλάζαμε τα φορτηγά ώστε:
(* Θεωρούμε ότι το ποδήλατο έχει 2 και το αυτοκίνητο 4 ρόδες. 
   Τα φορτηγά μπορεί να έχουν 6, 8, 10 ή και παραπάνω ρόδες, επομένως 
   πρέπει να επισημανθεί ο αριθμός όταν το ορίζουμε. *)
datatype VehicleWithWheels = Bicycle | Car | Truck of int;;

fun vehicle_to_str Bicycle = "bicycle has 2 wheels" 
|   vehicle_to_str Car = "car has 4 wheels"
|   vehicle_to_str (Truck n) = "truck has " ^ Int.toString n ^ " wheels";;
 > val vehicle_to_str = fn : VehicleWithWheels -> string
vehicle_to_str (Truck 10);;
 > val it = "truck has 10 wheels" : string
Οι τύποι αυτοί μπορεί να είναι και αναδρομικοί. Για παράδειγμα ο παρακάτω τύπος επιτρέπει τον ορισμό αριθμητικών παραστάσεων:
datatype Expression = 
  Num of int 
| Neg of Expression
| Plus of Expression * Expression 
| Mult of Expression * Expression;;
και η παρακάτω συνάρτηση επιτρέπει τον υπολογισμό τους.
fun evaluate (Num n) = n
|   evaluate (Neg e) = ~(evaluate e)
|   evaluate (Plus (e1, e2)) = (evaluate e1) + (evaluate e2)
|   evaluate (Mult (e1, e2)) = (evaluate e1) * (evaluate e2);;
 > val evaluate = fn : Expression -> int

(* Υπολογισμός της παράστασης: 10 + 5 * (7 - 3) = 30. *)
evaluate (Plus(Num 10, Mult(Num 5, Plus(Num 7, Neg(Num 3)))));;
 > val it = 30 : int

Οι τύποι μπορεί να είναι και πολυμορφικοί. Για παράδειγμα, ένας τύπος για λίστες μπορεί να οριστεί ως εξής:
datatype 'a MyList = Empty | Item of 'a * 'a MyList
και συναρτήσεις όπως η length, για την εύρεση του μήκους μίας λίστας, μπορούν να οριστούν ως εξής:
(* Συνάρτηση που επιστρέφει το μήκος της λίστας. *)
fun length Empty = 0
|   length (Cons(x, t)) = 1 + length t;;
 > val length = fn : 'a MyList -> int

(* Ορισμός μίας λίστας με τα στοιχεία 10, 20 και 30. *)
val ls = Cons(10, Cons(20, Cons(30, Empty)));;
length ls;;
 > val it = 3 : int
Αντίστοιχα με τις λίστες, μπορούν να οριστούν και άλλες δομές, όπως τα δυαδικά δένδρα:
datatype 'a BTree = Leaf | Branch of 'a * ('a BTree) * ('a BTree)

 Άπειρες δομές 

 Άπειρες λίστες 

Ο ορισμός αναδρομικών δομών και η δυνατότητα της αναπαράστασης των συναρτήσεων ως τιμές, επιτρέπει την αναπαράσταση λιστών με άπειρο μέγεθος. Ένας συνηθισμένος τρόπος είναι ο εξής:
(* Κάθε ένα κομμάτι της λίστας έχει ένα στοιχείο (τύπου 'a) 
   και μία συνάρτηση που όταν την καλέσουμε επιστρέφει 
   αναδρομικά την υπόλοιπη λίστα. *)
datatype 'a LazyList = Cons of 'a * (unit -> 'a LazyList)
Για παράδειγμα, η άπειρη ακολουθία ακεραίων ίσων ή μεγαλύτερων του , ορίζεται ως εξής:
fun integers_from n = 
   Cons(n, fn () => integers_from (n+1));;
Τα πρώτα  στοιχεία της λίστας μπορούν να ανακτηθούν ως εξής:
fun get_first _ 0 = []
|   get_first (Cons(x, t)) n = x::(get_first (t()) (n-1));;
 > val get_first = fn : 'a LazyList -> int -> 'a list
(* Επιστρέφει τους ακεραίους 1, 2, ... , 10. *)
get_first (integers_from 1) 10;;
 > val it = [1,2,3,4,5,6,7,8,9,10] : int list

Χρησιμοποιώντας αντίστοιχες συναρτήσεις με αυτές που υπάρχουν για λίστες (όπως η map), μπορούμε να ορίσουμε πιο σύνθετες ακολουθίες όπως τους ζυγούς αριθμούς ή τα τετράγωνα αριθμών:
(* Map για άπειρες λίστες. *)
fun lazy_list_map f (Cons(x, t)) =
   Cons(f x, fn () => lazy_list_map f (t()));;

(* Επιστρέφει μία ακολουθία με τους ζυγούς φυσικούς αριθμούς. *) 
val even_naturals = lazy_list_map (fn x => 2 * x) (integers_from 0);;
(* Επιστρέφει τους πρώτους 10 ζυγούς: 0, 2, ... , 18. *)
get_first even_naturals 10;;
 > val it = [0,2,4,6,8,10,12,14,16,18] : int list

(* Επιστρέφει μία ακολουθία με τα τετράγωνα αριθμών. *) 
val squares = lazy_list_map (fn x => 2 * x) (integers_from 0);;
(* Επιστρέφει τα 4 πρώτα τετράγωνα: 0, 1, 4, 9. *)
get_first squares 4;;
 > val it = [0,2,4,6] : int list

 Άπειρα δυαδικά δένδρα 

Αντίστοιχα με τις άπειρες λίστες μπορούν να οριστούν άπειρα δυαδικά δένδρα, π.χ. ως εξής:
datatype 'a LazyBTree = Branch of 'a * (unit -> 'a LazyBTree) * (unit -> 'a LazyBTree)

 Ταίριασμα προτύπων 

Η ML επιτρέπει το ταίριασμα προτύπων. Για παράδειγμα, την συνάρτηση factorial, μπορούμε να την γράψουμε ως εξής:
(* 1η Εκδοχή με την χρήση case ...  of *)
fun factorial n = case n of 
   0 => 1
|  n => n * factorial (n-1)

(* 2η Εκδοχή *)
fun factorial 0 = 1
|   factorial n = n * factorial (n-1)
Ο έλεγχος των προτύπων γίνεται με την σειρά που δίνονται οι περιπτώσεις. Επομένως, αν στην factorial αλλάξουμε την σειρά των περιπτώσεων, τότε το  θα ταιριάζει όλους τους ακεραίους και η δεύτερη περίπτωση δεν εκτελείται ποτέ. Αυτό οδηγεί στο σφάλμα Error: match redundant. Αντίστοιχα αν δεν καλύπτονται όλες οι περιπτώσεις, εμφανίζεται η προειδοποίηση Warning: match nonexhaustive.
fun factorial n = n * factorial (n-1)
|   factorial 0 = 1;;
 > stdIn: Error: match redundant
          n => ...
    -->   0 => ...

fun factorial 0 = 1;;
 > stdIn: Warning: match nonexhaustive
          0 => ...

 Εξαιρέσεις 

Η ML υποστηρίζει εξαιρέσεις. Με την σύνταξη raise <exception> εγείρεται μία εξαίρεση και με το handle <exception pattern> => <expression> διαχειρίζεται μία εξαίρεση. Για την συνάρτηση factorial που ορίσαμε παραπάνω, για αρνητικούς αριθμούς μπορούμε να εγείρουμε μία εξαίρεση ως εξής:

fun factorial n =
  if n = 0 then 1
  else if n > 0 then n * factorial (n-1)
  else raise Fail "Factorial only defined for non-negative integers."
(* Παρατηρήστε ότι ο τύπος της συνάρτησης δεν αλλάζει. *)
 > val factorial = fn : int -> int
Επομένως, άμα την καλέσουμε με αρνητικό ακέραιο, παίρνουμε:
factorial (~2)
 > uncaught exception Fail [Fail: Factorial only defined for non-negative integers.]
Εκτός από τις εξαιρέσεις της γλώσσας (Overflow, Div, Domain, Chr, Subscript), επιπλέον εξαιρέσεις μπορούν να οριστούν από τον χρήστη:
(* Εξαίρεση ειδικά ορισμένη για την συνάρτηση factorial. *)
exception FactorialException of string;;

fun factorial n =
  if n = 0 then 1
  else if n > 0 then n * factorial (n-1)
  else raise FactorialException "Given a negative integer.";;

factorial (~2);;
 > uncaught exception FactorialException

Ο παρακάτω κώδικας είναι ένα παράδειγμα διαχείρισης μίας εξαίρεσης:
(* Συνάρτηση που δέχεται μία λίστα και επιστρέφει 0 αν η λίστα είναι άδεια,
   διαφορετικά το παραγοντικό του πρώτου στοιχείου (και 1 αν είναι αρνητικός αριθμός). *)
fun hd_factorial ls = factorial (hd ls)
  handle Empty => 0
  |      _ => 1;;

hd_factorial [3, 1, 2];;
 > val it = 6 : int
hd_factorial [~2, 1, 3];;
 > val it = 1 : int
hd_factorial [];;
 > val it = 0 : int

Οι εξαιρέσεις μπορούν να χρησιμοποιηθούν και για την υλοποίηση της οπισθοδρόμησης.

 Πράξεις με παρενέργειες 

Η ML έχει πράξεις με παρενέργειες, όπως είναι η δήλωση ανάθεσης :=,
val a = ref 10;;
 > val a = ref 10 : int ref
a := !a + 5;;
 > val it = () : unit
a;;
 > val it = ref 15 : int ref

Δείτε επίσης
 OCaml

 Εξωτερικοί σύνδεσμοι 

Σελίδες υποστήριξης των εκδόσεων της ML:
 Standard ML of New Jersey
 Standard ML Family GitHub Project
 Poly/ML
 Moscow ML
 MLton
Βιβλία:
 ML for the Working Programmer
 Elements of ML Programming
 Purely Functional Data Structures, Βιβλίο για την υλοποίηση δομών δεδομένων στην ML (και γενικά σε συναρτησιακές γλώσσες).
 Programming With Standard ML
 The Little MLer
 Commentary on Standard ML
Tutorial:
 Four Lectures on Standard ML
 A gentle introduction to SML 
 Programming in Standard ML
 Notes on Programming Standard ML of New Jersey
 Programming in Standard ML ’97: A Tutorial Introduction
Πανεπιστημιακά μαθήματα:
 Γλώσσες Προγραμματισμού Ι, Διαλέξεις 4-5
 Γλώσσες Προγραμματισμού Ι: Βιντεομάθημα  Ενότητες 4-6
 Foundations of Computer Science, Εισαγωγή στην πληροφορική με ML.
 Principles of Programming, Εισαγωγή στον προγραμματισμό με ML.

 Παραπομπές 

Κατηγορία:Συναρτησιακές γλώσσες προγραμματισμού