The Basics
Running OCaml code
Ο απλούστερος τρόπος να ξεκινήσετε είναι να τρέξετε μια διαδραστική συνεδρία στον φυλλομετρητή σας, στο https://ocsigen.org/js_of_ocaml/2.7/files/toplevel/index.html.
Για να εγκαταστήσετε την Ocaml στον υπολογιστή σας, δείτε τις Οδηγίες Εγκατάστασης.
Για να δοκιμάσετε γρήγορα μικρές εκφράσεις της Ocaml, μπορείτε να δοκιμάσετε το ένα topelevel, ή ένα REPL (Read (διάβασε) - Eval (αποτίμησε) - Print (τύπωσε) Loop (βρόχο) ). Η εντολή ocaml
προσφέρει ένα πολύ βασικό toplevel (πρέπει να εγκαταστήσετε το rlwrap
μέσω του διαχειριστή πακέτων του συστήματός σας και να τρέξετε το rlwrap ocaml
για να έχετε ιστορικό). Αν μπορείτε να το εγκατασήστε μέσω του OPAM ή του διαχειριστή πακέτων του συστήματός σας προτίνουμε να χρησιμοποείσετε το utop toplevel, το οποίο έχει την ίδια βασική διεπαφή αλλά είναι πολύ πιο βολικό στη χρήση (ιστορικό εντολών, αυτόματη συμπλήρωση, και άλλα).
Γράψτε ;;
για να δηλώστε ότι έχετε τελειώσει να γράφετε μια δήλωση.
$ ocaml
OCaml version 4.10.0
# 1+1;;
- : int = 2
───────┬────────────────────────────────────────────────────────────┬─────
│ Welcome to utop version 1.18 (using OCaml version 4.02.3)! │
└────────────────────────────────────────────────────────────┘
Type #utop_help for help about using utop.
─( 10:12:16 )─< command 0 >───────────────────────────────────────────────
utop # 1 + 1;;
- : int = 2
Για να μεταγλωττίσετε ένα πρόγραμμα σε Ocaml με όνομα my_prog.ml
σε ένα εκτελέσιμο σε γλώσσα μηχανής, χρησιμοποιήστε το ocamlbuilld my_prog.native
:
$ mkdir my_project
$ cd my_project
$ echo 'let () = print_endline "Hello, World!"' > my_prog.ml
$ ocamlbuild my_prog.native
Finished, 4 targets (0 cached) in 00:00:00.
$ ./my_prog.native
Hello, World!
Δείτε Μεταγλωττίζοντας Πρότζεκ σε Ocaml για περισσότερες πληροφορίες.
Σχόλια
Τα σχόλια στη Ocaml ξεκινάν με (*
και τελειώνουν με *)
κάπως έτσι:
(* Αυτό είναι ένα σχόλιο μιας γραμμής. *)
(* Αυτό είναι ένα
* σχόλιο σε
* πολλές γραμμές.
*)
Με άλλα λόγια, η σύμβαση για σχόλια είναι παρόμοια με την αρχική C
(/* ... */
). Δεν υπάρχει μέχρι στιγμής συντακτικό για σχόλια μας γραμμής (όπως
# ...
στην Perl ή // ...
σε C99/C++/Java).
Η Ocaml προσμετρά τα εμφολευμένα σχόλια (* ... *)
, και αυτό σας επιτρέπει να σχολιο-ποιείτε
περιοχές κώδικα πολύ εύκολα:
(* Αυτός ο κώδικας δε λειτουργεί για κάποιο λόγο ...
(* Primality test. *)
let is_prime n =
(* note to self: ask about this on the mailing lists *) XXX;;
*)
Καλώντας Συναρτήσεις
Ας υποθέσουμε ότι έχετε γράψει μια συνάρτηση - θα την ονομάσουμε repeated
- η οποία
παίρνει μια συμβολοσειρά s
και έναν αριθμό n
και επιστρέφει μια νέα συμβολοσειρά η οποία
περιέχει την αρχική συμβολοσειρά s
επαναλαμβανόμενη n
φορές.
Στις πιο πολλές γλώσσες της οικογένειας της c μια κλήση σε αυτή τη συνάρτηση θα έμοιαζε κάπως έτσι:
repeated ("hello", 3) /* this is C code */
Αυτό σημαίνει "κάλεσε τη συνάρτηση repeated
με δύο ορίσματα, πρώτο
όρισμα τη συμβολοσειρά hello
και δεύτερο όρισμα τον αριθμό 3"
Η Ocaml, παρόμοια με άλλες συναρτησιακές γλώσσες, χρησιμοποιεί διαφορετικό συντακτικό, κάτι που γίνεται συχνά αίτιο λαθών. Εδώ φαίνεται ο τρόπος που θα γράφαμε ότι και παραπάνω σε Ocaml:
repeated "hello" 3 (* this is OCaml code *)
Προσοχή: — δεν βάζουμε παρενθέσεις, και δεν βάζουμε κόμμα μεταξύ των ορισμάτων.
Η σύνταξη repeated ("hello", 3)
έχει νόημα στην OCaml. Σημαίνει
"κάλεσε τη συνάρτηση repeated
με ΕΝΑ όρισμα, το οποίο είναι μια δομή
'ζεύγος' δύο στοιχείων". Φυσικά αυτό θα ήταν λάθος, αφού η συνάρτηση repeated
περιμένει δύο ορίσματα, και όχι ένα, και το πρώτο όρισμα πρέπει να είναι
συμβολοσειρά , και όχι ζεύγος. Αλλά ας μην ανησυχούμε για τα ζεύγη ("tuples", "τούπλες")
ακόμα. Αντίθετα ας συγκρατήστε απλά ότι είναι λάθος να βάζουμε παρενθέσεις και
κόμματα γύρω από τα ορίσματα μιας κλήσης συνάρτησης.
Ας πάρουμε μια άλλη συνάρτηση prompt_string
— η οποία παίρνει μια συμβολοσειρά
ως ερώτηση στο χρήστη και επιστρέφει την απάντηση του χρήστη ως μια συμβολοσειρά.
Θέλουμε να περάσουμε αυτή την απάντηση στην repeated repeated
.
Ορίστε οι τρόποι να το κάνουμε αυτό σε C και σε Ocaml:
/* C κώδικας: */
repeated (prompt_string ("Όνομα παρακαλώ: "), 3)
(* OCaml κώδικας: *)
repeated (prompt_string "Όνομα παρακαλώ: ") 3
Παρατηρείστε προσεκτικά τις παρενθέσεις και το κόμμα που λείπει. Στην
περίπτωση της Ocaml, οι παρενθέσεις περικλείουν το πρώτο όρισμα της repeated
επειδή αυτό το όρισμα είναι το αποτέλεσμα μιας άλλης κλήσης συνάρτησης.
Γενικά ο κανόνα είναι: "παρένθεση γύρω από την πλήρη κλήση συνάρτησης -
όχι παρενθέσεις γύρω από τα ορίσματα μιας κλήσεις συνάρτησης". Να μερικά
παραδείγματα:
f 5 (g "hello") 3 (* f has three arguments, g has one argument *)
f (g 3 4) (* f has one argument, g has two arguments *)
# repeated ("hello", 3) (* OCaml will spot the mistake *);;
Error: This expression has type 'a * 'b
but an expression was expected of type string
Ορίζοντας μια συνάρτηση
Ξέρετε πώς να ορίσετε μια συνάρτηση (ή στατική μέθοδο για όσους μιλάνε Java) στις ήδη υπάρχουσες γλώσσες. Πώς το κάνουμε αυτό στην Ocaml;
Το συντακτικό της Ocaml είναι ευχάριστα συνοπτικό. Να μια συνάρτηση που παίρνει ως ορίσματα δύο αριθμούς μεταβλητής υποδιαστολής και υπολογίζει το μέσο όρο τους:
let average a b =
(a +. b) /. 2.0;;
Γράψτε το παραπάνω σε ένα "toplevel" της Ocaml (στα συστήματα Unix γράψτε
την εντολή ocaml
στον φλοιό-terminal) και θα δείτε το παρακάτω:
# let average a b =
(a +. b) /. 2.0;;
val average : float -> float -> float = <fun>
Αν παρατηρείστε προσεκτικά τον ορισμό της συνάρτησης, και επίσης και τι εμφανίζει η Ocaml αφού το γράψετε, θα σας δημιουργηθούν κάποιες απορίες:
- Τι κάνουν όλες αυτές οι τελείες μέσα στον κώδικα;
- Τι σημαίνει αυτό το πράμα:
float -> float -> float
;
Θα απαντήσουμε αυτές τις ερωτήσεις στα επόμενα τμήματα, αλλά πρώτα ας ορίσουμε την ίδια συνάρτηση σε C (ο ορισμός σε Java είναι αρκετά παρόμοιος με τη C), και ελπίζουμε ότι θα προκληθούν κι άλλες ερωτήσεις. Ορίστε η συνάρτηση ορισμένη σε C:
double average (double a, double b)
{
return (a + b) / 2;
}
Δείτε πόσο πιο συνοπτικά την ορίσαμε με την Ocaml παραπάνω. Ελπίζουμε ότι θα ρωτάτε:
- Γιατί δεν πρέπει να ορίσουμε τους τύπους των
a
καιb
στην περίπτωση της OCaml; Πώς ξέρει η Ocaml τους τύπους αυτούς; (άραγε ξέρει όντως η Ocaml ποιοι είναι οι τύποι, ή η Ocaml έχει εντελώς δυναμικό σύστημα τύπων; ) - Στη C το
2
μετατρέπεται κάτω από το τραπέζι σεdouble
, δε μπορεί η Ocaml να κάνει το ίδιο; - Με ποιον τρόπο γράφουμε
return
στην Ocaml;
Ας δώσουμε μερικές απαντήσεις.
- Η Ocaml έχει αυστηρά στατικός σύστημα τύπων (με άλλα λόγια δεν αλλάζει τίποτα δυναμικά μεταξύ int, float και string όπως στην Perl).
- Η Ocaml χρησιμοποιεί διεπαφή τύπων για να εκμαιεύσει τους τύπους, έτσι ώστε να μη χρειάζεται να το κάνετε εσείς. Αν χρησιμοποιείτε το toplevel της Ocaml όπως παραπάνω, τότε η Ocaml θα σας πει το συμπερασμένο τύπο για τη συνάρτησή σας.
- H OCaml δεν κάνει καμιά μετατροπή τύπων κάτω από το τραπέζι.Αν θέλετε
float , πρέπει να γράψετε
2.0
επειδή το2
είναι integer. Η OCaml δεν μετατρέπει αυτόματα μεταξύ int, float, string ή οποιονδήποτε άλλο τύπο. - Μια παρενέργεια του συμπερασμού τύπων στην Ocaml είναι ότι οι συναρτήσεις
(συμπεριλαμβανομένων και των τελεστών) δε μπορούν να υπερφορτωθούν.
Η OCaml ορίζει το
+
ως την πρόσθεση για ακεραίους. Για να προσθέσετε floats πρέπει να χρησιμοποιείστε το+.
(προσοχή στην τελεία). Παρόμοια χρησιμοποιείστε-.
,*.
,/.
για τις πράξεις στους floats. - Η OCaml δεν έχει δεσμευμένη λέξη
return
— η τελευταία έκφραση σε μια συνάρτηση γίνεται το αποτέλεσμα της συνάρτησης αυτής αυτόματα.
Θα παρουσιάσουμε περισσότερες λεπτομέρειες σε επόμενα τμήματα και κεφάλαια.
Βασικοί τύποι
Οι βασικοί τύποι στην Ocaml είναι:
OCaml type Range
int 31-bit προσημασμένος ακέραιος (περίπου +/- 1 δισεκατομμύριο) σε 32-bit
επεξεργαστές, ή 63-bit προσημασμένος ακέραιος σε 64-bit επεξεργαστές
float IEEE αριθμός κινητής υποδιαστολής διπλής ακρίβειας, ισοδύναμο με double της C
bool Μπουλιανή μεταβλητή true ή false
char Ένας 8-bit χαρακτήρας
string Μια συμβολοσειρά
unit Γράφετε ως ()
Η Ocaml χρησιμοποιεί ένα από τα bits σε έναν int εσωτερικά, ώστε να μπορεί
να διαχειρίζεται αυτόματα τη χρησιμοποίηση μνήμης (συλλογή σκουπιδιών).
Γι αυτό ο βασικός τύπος int
έχει 31-bits και όχι 32 (63 σε 64-bits
επεξεργαστές). Στην πράξη αυτό δεν είναι πρόβλημα εκτός από μερικές
ειδικές περιπτώσεις. Για παράδειγμα αν μετράτε πράγματα σε έναν βρόχο,
τότε η Ocaml σας περιορίζει στα 1 δισεκατομμύριο αντί για 2 δισεκατομμύρια.
Αυτό δεν είναι πρόβλημα καθώς αν μετράτε πράγματα κοντά σε αυτό το όριο
θα έπρεπε να χρησιμοποιείτε βιβλιοθήκες μεγάλων αριθμών (όπως τα modules Nat
και Big_int
της Ocaml). Παρόλα αυτά αν πρέπει να διαχειριστείτε αντικείμενα
όπως τύπο μεγέθους 32-bits (π.χ. γράφετε κώδικα για κρυπτογραφικές ή δικτυακές
εφαρμογές), η Ocaml σας παρέχει έναν τύπο nativeint
ο οποίος ταυτίζεται με
τον τύπο ακεραίων του επεξεργαστή σας.
Η Ocaml δεν έχει κάποιο βασικό μη προσημασμένο τύπο για ακέραιους, αλλά
μπορείτε να έχετε τα ίδια αποτελέσματα με τη χρήση του nativeint
. Η Ocaml
δε φαίνεται να έχει αριθμούς κινητής υποδιαστολής απλής ακρίβειας.
Η Ocaml παρέχει τον τύπο char
ο οποίος χρησιμοποιείτε για χαρακτήρες
οι οποίο γράφονται για παράδειγμα ως 'x'
. Δυστυχώς ο τύπος char
δεν
υποστηρίζει Unicode ή UTF-8. Αυτό είναι ένα σημαντικό πρόβλημα στην Ocaml
που πρέπει να διορθωθεί, αλλά για την ώρα υπάρχου πλήρεις βιβλιοθήκες για Unicode
που υποστηρίζουν Unicode και UTF-8.
Οι συμβολοσειρές δεν είναι απλά λίστες χαρακτήρων. Έχουν το δικό τους, πιο αποδοτικό τρόπο αναπαράστασης.
Ο τύπος unit
μοιάζει σαν το void
της C, αλλά θα μιλήσουμε γι αυτόν
παρακάτω.
Μετατροπές τύπων άμεσα και έμμεσα
Στις γλώσσες της οικογένειας της C οι ακέραιοι μπορούν να γίνουν αριθμοί
κινητής υποδιαστολής υπό κάποιες προϋποθέσεις. Για παράδειγμα αν γράψετε
1 + 2.5
τότε το πρώτο όρισμα (που είναι ακέραιος) θα γίνει αριθμός κινητής υποδιαστολής,
και το αποτέλεσμα θα είναι και αυτό αριθμός κινητής υποδιαστολής.
Είναι σαν να είχατε γράψει ((double) 1) + 2.5
, αλλά έμμεσα.
Η Ocaml δεν κάνει ποτέ έμμεση μετατροπή τύπων. Στην Ocaml το 1 + 2.5
θα παράξει σφάλμα τύπου. Ο τελεστής +
στην OCaml απαιτεί δύο ακέραιους
ως ορίσματα και εδώ του δίνουμε έναν ακέραιο και έναν αριθμό κινητής
υποδιαστολής οπότε μας λέει το εξής:
# 1 + 2.5;;
Error: This expression has type float but an expression was expected of type
int
(Σε απλά ελληνικά αυτό σημαίνει "μου έδωσες έναν αριθμό κινητής υποδιαστολής εδώ, αλλά εγώ περίμενα ακέραιο").
Για να προσθέσετε δύο αριθμούς κινητής υποδιαστολής πρέπει να χρησιμοποιήσετε
διαφορετικό τελεστή, τον +.
(προσοχή στην τελεία).
Η Ocaml δεν μετατρέπει τους ακέραιους σε αριθμούς κινητής υποδιαστολής οπότε και το παρακάτω είναι επίσης λάθος:
# 1 +. 2.5;;
Error: This expression has type int but an expression was expected of type
float
Τώρα η Ocaml παραπονιέται για το πρώτο όρισμα.
Και τι γίνεται αν θέλετε να προσθέσετε έναν ακέραιο με έναν κινητής υποδιαστολής;
(Έστω i
και f
αντίστοιχα). Στην OCaml πρέπει να κάνετε άμεση μετατροπή τύπων:
(float_of_int i) +. f
Η float_of_int
είναι μια συνάρτηση που παίρνει int
και επιστρέφει float
.
Υπάρχουν πάρα πολλές άλλες τέτοιες συναρτήσεις που έχουν ονόματα όπως
int_of_float
, char_of_int
, int_of_char
, string_of_int
και τα λοιπά,
και κάνουν συνήθως αυτό που περιμένετε.
Αφού η μετατροπή int
σε float
είναι αρκετά συχνή, η συνάρτηση float_of_int
έχει ένα συντομότερο συνώνυμο: το παραπάνω παράδειγμα μπορεί να γραφτεί και ως:
float i +. f
(Παρατηρήστε ότι σε αντίθεση με τη C, είναι εντελώς νόμιμο στην Ocaml για έναν τύπο και μια συνάρτηση να έχουν το ίδιο όνομα.)
Άμεση ή έμμεση μετατροπή; Τι είναι καλύτερο;
Μπορείτε να νομίζετε πως η άμεση μετατροπή τύπων είναι άσχημη, ακόμη και χρονοβόρα, και έχετε δίκιο. Όμως κανείς μπορεί να επιχειρηματολογήσει υπέρ της. Αρχικά, η Ocaml χρειάζεται αυτή την άμεση μετατροπή για να μπορεί να εκτελεί το συμπερασμό τύπων, ένα εργαλείο που μπορεί να σας γλυτώσει πολύ χρόνο και σίγουρα αναπληρώνει την επιπλέον πληκτρολόγηση που απαιτεί η άμεση μετατροπή. Κατά δεύτερον αν έχετε αφιερώσει χρόνο αποσφαλματώνοντας προγράμματα γραμμένα σε C θα ξέρετε ότι (α) λάθη που οφείλονται στην έμμεση μετατροπή τύπων είναι δύσκολο να εντοπιστούν και (β) χάνετε τον πιο πολύ χρόνο κοιτώντας την οθόνη προσπαθώντας να εντοπίσετε που έχουν συμβεί οι έμμεσες μετατροπές. Κάνοντας μόνο άμεσες μετατροπές σας βοηθά στην αποσφαλμάτωση. Τρίτον μερικές μετατροπές (ειδικά από ακέραιους σε κινητής υποδιαστολής) είναι πολύ ακριβές λειτουργίες. Γι' αυτό θα ήταν καλό να μην είναι κρυμμένες.
Συνήθεις συναρτήσεις και αναδρομικές συναρτήσεις
Σε αντίθεση με τις γλώσσες που προήλθαν από τη C, μια συνάρτηση δεν είναι
αναδρομική εκτός αν το δηλώσετε ρητά γράφοντας let rec
αντί για let
. Να
ένα παράδειγμα αναδρομικής συνάρτησης:
# let rec range a b =
if a > b then []
else a :: range (a+1) b;;
val range : int -> int -> int list = <fun>
Προσέξτε ότι η range
καλεί τον εαυτό της.
Η μόνη διαφορά μεταξύ let
και let rec
είναι το πεδίο στο οποίο είναι
ορατό το όνομα της συνάρτησης. Αν η παραπάνω συνάρτηση είχε δηλωθεί απλά
με let
, τότε η κλήση στη range
θα έψαχνε να βρει για μια ήδη δηλωμένη
συνάρτηση με όνομα range
, και όχι τη συνάρτηση που ορίζουμε τώρα.
Γράφοντας let
σας επιτρέπει να επανα-ορίσετε μια τιμή όσον αφορά
τον προηγούμενο ορισμό της. Για παράδειγμα:
# let positive_sum a b =
let a = max a 0
and b = max b 0 in
a + b;;
val positive_sum : int -> int -> int = <fun>
Αυτός ο επανα-ορισμός κρύβει το προηγούμενο δέσιμο των a
και b
από
τον ορισμό της συνάρτησης. Σε μερικές περιπτώσεις οι προγραμματιστές
προτιμούν αυτό το μοτίβο από το να χρησιμοποιήσουν ένα νέο όνομα μεταβλητής
(let a_pos = max a 0
) μιας και καθιστά το παλιό δέσιμο μη προσβάσιμο
και άρα μόνο οι τελευταίες τιμές των a
και b
είναι προσβάσιμες.
Δεν υπάρχει διαφορά στην απόδοση μεταξύ συναρτήσεων που ορίστηκαν με let
και με συναρτήσεις που ορίστηκαν με let rec
, οπότε αν προτιμάτε μπορείτε
πάντα να χρησιμοποιήσετε το let rec
και να έχετε την ίδια σημασιολογία
με της γλώσσες της οικογένειας της C.
Τύποι συναρτήσεων
Εξαιτίας του συμπερασμού τύπων, σπάνια θα χρειαστεί να δηλώσετε ρητά
τον τύπο μιας συνάρτησης. Όμως η Ocaml συχνά τυπώνει τον τύπο που έχει
συμπεράνει για μια συνάρτηση, οπότε χρειάζεται να ξέρετε το συντακτικό
των τύπων. Για μια συνάρτηση f
που παίρνει ορίσματα arg1
,
arg2
, ... argn
, και επιστρέφει τύπο rettype
, ο μεταγλωττιστής θα τυπώσει:
f : arg1 -> arg2 -> ... -> argn -> rettype
Η σύνταξη με το βέλος φαίνεται παράξενη τώρα, αλλά μόλις δούμε την έννοια του currying θα καταλάβετε γιατί επιλέχτηκε. Για την ώρα θα δούμε μόνο μερικά παραδείγματα.
Η συνάρτηση repeated
που παίρνει μια συμβολοσειρά και έναν ακέραιο
και επιστρέφει μια συμβολοσειρά έχει τύπο:
repeated : string -> int -> string
Η συνάρτηση average
που παίρνει δύο αριθμούς κινητής υποδιαστολής και
επιστρέφει έναν αριθμό κινητής υποδιαστολής έχει τύπο:
average : float -> float -> float
Η συνάρτηση int_of_char
που παρέχεται από την Ocaml:
int_of_char : char -> int
Αν μια συνάρτηση δεν επιστρέφει τίποτα (void
για προγραμματιστές που
έρχονται από C και Java), τότε γράφουμε ότι επιστρέφει τύπο unit
. Εδώ
για παράδειγμα βλέπουμε τον τύπο της συνάρτησης που κάνει ότι και η fputc
στη C:
output_char : out_channel -> char -> unit
Πολυμορφικές συναρτήσεις
Τώρα για κάτι λίγο πιο περίεργο. Τι συμβαίνει όταν μια συνάρτηση μπορεί να πάρει οτιδήποτε ως όρισμα; Να μια περίεργη συνάντηση που παίρνει ένα όρισμα, αλλά το αγνοεί και επιστρέφει πάντα 3:
let give_me_a_three x = 3
Ποιος είναι ο τύπος της συνάρτησης; Στην Ocaml χρησιμοποιούμε μια ειδική μεταβλητή τύπων και εννοούμε "ότι τύπος θες". Είναι ένα χαρακτήρας μονών εισαγωγικών ακολουθούμενος από ένα γράμμα. Ο τύπος της από πάνω συνάρτησης θα γραφόταν κανονικά ως:
give_me_a_three : 'a -> int
Όπου 'a
σημαίνει στην πραγματικότητα οποιοσδήποτε τύπος. Μπορείτε
για παράδειγμα να ονομάσετε αυτή τη συνάρτηση ως give_me_a_three "foo"
ή give_me_a_three 2.0
. Και τα δύο είναι νόμιμες εκφράσεις στην Ocaml.
Δε θα σας είναι ακόμα σαφές γιατί οι πολυμορφικές συναρτήσεις είναι χρήσιμες αλλά είναι πολύ χρήσιμες και πολύ συνηθισμένες, οπότε και θα τις μελετήσουμε αργότερα. (Συμβουλή: ο πολυμορφισμός είναι κάτι σαν τα templates στην C++ ή τα generics στη Java 1.5).
Διεπαφές τύπων
Είδαμε λοιπόν ότι οι συναρτησιακές γλώσσες προγραμματισμού έχουν πολλά ενδιαφέροντα χαρακτηριστικά, και η Ocaml είναι η γλώσσα που έχει όλα αυτά τα χαρακτηριστικά μαζεμένα μέσα της ταυτόχρονα, κάνοντάς τη μια πολύ πρακτική γλώσσα για αληθινούς προγραμματιστές. Το περίεργο είναι ότι τα πιο πολλά από τα εργαλεία της δεν έχουν καμία σχέση με τον συναρτησιακό προγραμματισμό. Στην πραγματικότητα έχουμε ήδη φτάσει στο πρώτο χρήσιμο χαρακτηριστικό και δεν έχουμε μιλήσει καν γιατί ο συναρτησιακός προγραμματισμός λέγεται "συναρτησιακός". Το χαρακτηριστικό αυτό είναι ο συμπερασμός τύπων
Με απλά λόγια: δε χρειάζεται να δηλώνεται τους τύπους των συναρτήσεων σας και τον μεταβλητών σας, επειδή η Ocaml θα το κάνει μόνη της για εσάς!
Ακόμα, η Ocaml πάει ένα βήμα παραπέρα και ελέγχει αν όλοι οι τύποι που έχετε δημιουργήσει ταιριάζουν. Ακόμα και μεταξύ διαφορετικών αρχείων.
Αλλά η Ocaml είναι και μια πρακτική γλώσσα, και για αυτό το λόγο περιέχει κερκόπορτες μέσα στο σύστημα τύπων της που σας επιτρέπουν να παρακάμψετε τους ελέγχους που εκτελεί για τις σπάνιες περιπτώσεις που έχετε λόγο να το κάνετε. Μόνο οι guru της γλώσσας είναι πιθανό να βρουν λόγο να παρακάμψουν το σύστημα τύπων όμως.
Ας επιστρέψουμε όμως στη συνάρτηση average
την οποία γράψαμε στο
toplevel της Ocaml.
# let average a b =
(a +. b) /. 2.0;;
val average : float -> float -> float = <fun>
Ως εκ θαύματος η Ocaml κατάλαβα από μόνη της ότι η συνάρτηση παίρνει δύο αριθμούς κινητής υποδιαστολής και επιστρέφει επίσης έναν αριθμό κινητής υποδιαστολής.
Πώς το κατάφερε αυτό; Αρχικά βλέπει που χρησιμοποιούνται τα a
και b
,
συγκεκριμένα στην έκφραση (a +. b)
. Όμως το +.
είναι μια συνάρτηση
που πάντα παίρνει δύο αριθμούς κινητής υποδιαστολής ως ορίσματα, άρα
με απλή λογική επαγωγή τα a
και b
πρέπει να είναι και τα δύο αριθμοί
κινητής υποδιαστολής.
Δεύτερον το '/.' επιστρέφει έναν αριθμό κινητής υποδιαστολής, και αυτή
η τιμή είναι η ίδια με την τιμή επιστροφής της συνάρτησης average
. Άρα
η average
πρέπει να επιστρέφει έναν αριθμό κινητής υποδιαστολής. Το
συμπέρασμα είναι ότι η average
έχει τελικά τύπο:
average : float -> float -> float
Ο συμπερασμός τύπων είναι προφανώς εύκολος για τόσο μικρά προγράμματα,
αλλά λειτουργεί και σε πολύ μεγαλύτερα, και σας γλυτώνει τρομερό χρόνο
αφού αφαιρεί μια ολόκληρη κατηγορία λαθών που προκαλούν συχνά segfaults,
NullPointerException
s και ClassCastException
s σε άλλες γλώσσες (ή
σημαντικές αλλά συχνά παραβλεπόμενες προειδοποιήσεις, όπως στην Perl).