Αποκτήστε τον κώδικα: learnocaml.ml
Η OCaml είναι μία strictly evaluated συναρτησιακή γλώσσα με κάποια στοιχεία προστακτικού προγραμματισμού.
Μαζί με την StandardML και τις διαλέκτους της, ανήκει στην οικογένεια ML γλωσσών. Η F# είναι επίσης αρκετά επιρρεασμένη από την OCaml.
Ακριβώς όπως η StandardML, η OCaml διαθέτει έναν interpreter, που μπορεί να χρησιμοποιηθεί διαδραστικά, αλλά και έναν compiler. Το εκτελέσιμο αρχείο του interpreter κανονικά έχει το όνομα "ocaml" και ο compiler έχει το όνομα "ocamlopt". Υπάρχει και ένας bytecode compiler "ocamlc", αλλά δεν υπάρχουν πολλοί λόγοι να το χρησιμοποιήσει κάποιος.
Είναι ισχυρά και στατικά τυποποιημένη. Παρ'όλα αυτά , δεν χρειάζεται ο προγραμματιστής να δηλώνει τους τύπους, καθώς συμπερασμός τύπων γίνεται με τον αλγόριθμο του συστήματος τύπων Hindley-Milner. Αυτό κάνει τις δηλώσεις τύπων μη αναγκαίες στις περισσότερες περιπτώσεις, αλλά μπορεί να είναι δύσκολο στην αρχή.
Όταν είμαστε στο toplevel της OCaml (read-eval-print-loop), η OCaml τυπώνει τον τύπο που συμπεραίνει όταν εισάγουμε μια έκφραση.
# let inc x = x + 1 ;;
val inc : int -> int = <fun>
# let a = 99 ;;
val a : int = 99
Για ένα source αρχείο μπορούμε να χρησιμοποιούμε την εντολή "ocamlc -i /path/to/file.ml" στο terminal για να τυπώσει όλα τα ονόματα και τους τύπους.
$ cat sigtest.ml
let inc x = x + 1
let add x y = x + y
let a = 1
$ ocamlc -i ./sigtest.ml
val inc : int -> int
val add : int -> int -> int
val a : int
Σημειώστε ότι τα type signatures των συναρτήσεων με πολλά ορίσματα είναι γραμμένα σε curried form. Μια συνάρτηση με πολλά ορίσματα μπορεί να αναπαρασταθεί ως σύνθεση συναρτήσεων με μόνο ένα όρισμα. Η "f(x,y) = x + y" από το παράδειγμα, όταν εφαρμόζεται στα ορίσματα 2 και 3 είναι ισοδύναμη με την εφαρμογή της "f0(y) = 2 + y" στο 3. Γι' αυτό έχει τύπο "int -> int -> int".
(*** Comments ***)
(* Τα σχόλια περικλείονται σε (* και *). Μπορούν να είναι και εμφωλευμένα *)
(* Δεν υπάρχει ειδικό σύμβολο για σχόλια μιας γραμμής *)
(*** Μεταβλητές και Συναρτήσεις ***)
(* Οι εκφράσεις διαχωρίζονται από διπλό semicolon, ";;".
Σε πολλές περιπτώσεις είναι περιττό, αλλά εδώ θα το χρησιμοποιούμε σε
κάθε έκφραση για ευκολότερο copy-paste στο interpreter shell.
Το να χρησιμοποιούμε περιττά ;; σε αρχεία κώδικα θεωρείται συνήθως
κακό στυλιστικά. *)
(* Οι δηλώσεις μεταβλητών και συναρτήσεων χρησιμοποιούν το keyword "let" *)
let x = 10 ;;
(* Η OCaml επιτρέπει χαρακτήρες μονών εισαγωγικών σε identifiers.
το μονό εισαγωγικό δεν έχει κάποια σημασία σε αυτή την περίπτωση,
χρησιμοποιείται συνήθως σε περιπτώσεις που σε άλλες γλώσσες χρησιμοποιούμε
ονόματα όπως "foo_tmp". *)
let foo = 1 ;;
let foo' = foo * 2 ;;
(* Από τη στιγμή που ο compiler της OCaml συμπεραίνει τους τύπους αυτόματα,
κανονικά δεν χρειάζεται να δηλώνουμε ρητά τον τύπο ορισμάτων. Παρ'όλα αυτά
μπορούμε να το κάνουμε αν θέλουμε ή χρειάζεται *)
let inc_int (x: int) : int = x + 1 ;;
(* Μία από αυτές τις περιπτώσεις που είναι αναγκαίο να δηλώσουμε ρητά τύπους
είναι για να λύσουμε την αμφισημία μεταξύ δύο record types που έχουν πεδία με
όμοια ονόματα. Η εναλλακτική είναι να βάλουμε αυτούς τους τύπους σε modules,
αλλά και τα δύο αυτά θέματα είναι εκτός του σκοπού αυτού το μαθήματος. *)
(* Πρέπει να δηλώνουμε ότι μία συνάρτηση είναι αναδρομική με "rec". *)
let rec factorial n =
if n = 0 then 1
else n * factorial (n-1)
;;
(* H εφαρμογή συναρτήσεων συνήθως δεν χρειάζεται παρενθέσεις γύρω από ορίσματα *)
let fact_5 = factorial 5 ;;
(* ...εκτός αν τα ορίσματα είναι εκφράσεις *)
let fact_4 = factorial (5-1) ;;
let sqr2 = sqr (-2) ;;
(* Κάθε συνάρητση πρέπει να έχει τουλάχιστον ένα όρισμα.
Από τη στιγμή που κάποιες συναρτήσεις, από τη φύση τους, δεν παίρνουν κάποιο
όρισμα, υπάρχει ο τύπος "unit" που έχει μόνο μία τιμή,
την οποία γράφουμε ως "()". *)
let print_hello () = print_endline "hello world" ;;
(* Προσέχετε ότι πρέπει να γράφουμε το "()" ως όρισμα και όταν την καλούμε. *)
print_hello () ;;
(* Το να καλούμε μια συνάρτηση με λιγότερα ορίσματα από όσα δέχεται
δεν προκαλεί πρόβλημα, απλά παράγει μια νέα συνάρτηση. *)
let make_inc x y = x + y ;; (* make_inc is int -> int -> int *)
let inc_2 = make_inc 2 ;; (* inc_2 is int -> int *)
inc_2 3 ;; (* Αποτιμάται σε 5 *)
(* Μπορούμε να χρησιμοποιούμε πολλές εκφράσεις στο σώμα μιας συνάρτησης.
Η αποτίμηση της τελευταίας έκφρασης είναι η τιμή που επιστρέφει η συνάρτηση.
Όλες οι ενδιάμεσες εκφράσεις πρέπει να είναι τύπου "unit".
Αυτό είναι ιδιαίτερα χρήσιμο όταν γράφουμε σε προστακτικό στυλ, η απλούστερη
μορφή αυτού είναι η εισαγωγή ενός debug print. *)
let print_and_return x =
print_endline (string_of_int x);
x
;;
(* Ως συναρτησιακή γλώσσα η OCaml δεν έχει "procedures" (διαδικασίες).
Κάθε συνάρτηση πρέπει να επιστρέφει κάτι. Οπότε, συναρτήσεις που δεν
επιστρέφουν κάτι και καλούνται μόνο για τις παρενέργειες τους,
όπως η print_endline, επιστρέφουν τιμή τύπου "unit". *)
(* Οι ορισμοί μπορούν να γίνουν αλυσιδωτά με τη δομή "let ... in".
Αυτό είναι περίπου το ίδιο με το να αναθέτουμε τιμές σε πολλές μεταβλητές
πριν τις χρησιμοποιήσουμε σε εκφράσεις σε προστακτικές γλώσσες. *)
let x = 10 in
let y = 20 in
x + y ;;
(* Εναλλακτικά μπορούμε να χρησιμποιούμε τη δομή "let ... and ... in".
Αυτό είναι εξαιρετικά χρήσιμο για αμοιβαία αποκλειόμενες συναρτήσεις,
όπυ με "let .. in", ο compiler θα παραπονιόταν για unbound values *)
let rec
is_even = function
| 0 -> true
| n -> is_odd (n-1)
and
is_odd = function
| 0 -> false
| n -> is_even (n-1)
;;
(* Οι ανώνυμες συναρτήσεις χρησιμοποιούν την εξής σύνταξη: *)
let my_lambda = fun x -> x * x ;;
(*** Τελεστές ***)
(* Δεν υπάρχει ιδιαίτερη διάκριση ανάμεσα σε τελεστές και συναρτήσεις.
Κάθε τελεστής μπορεί να κληθεί ως συνάρτηση. *)
(+) 3 4 (* Same as 3 + 4 *)
(* Υπάρχει ένας αριθμός built-in τελεστών. Ένα ασυνήθιστο χαρακτηριστικό είναι
ότι η OCaml δεν μπορεί να κάνει έμμεση μετατροπή τύπων
ανάμεσα σε ακεραίους και floats, επίσης, χρησιμοποιεί διαφορετικούς τελεστές
για τους floats (αριθμούς κινητής υποδιαστολής) *)
12 + 3 ;; (* Πρόσθεση ακεραίων. *)
12.0 +. 3.0 ;; (* Πρόσθεση κινητής υποδιαστολής. *)
12 / 3 ;; (* Διαίρεση ακεραίων. *)
12.0 /. 3.0 ;; (* Διαίρεση κινητής υποδιαστολής. *)
5 mod 2 ;; (* Υπόλοιπο. *)
(* Το ενός-ορίσματος μείον είναι αξιοσημείωτη εξαίρεση, είναι πολυμορφικό.
Ωστόσο, έχει καθαρές μορφές ακεραίων και float. *)
- 3 ;; (* Πολυμορφικό, ακέραιοι *)
- 4.5 ;; (* Πολυμορφικό, float *)
~- 3 (* Μόνο για integer *)
~- 3.4 (* Type error *)
~-. 3.4 (* Μόνο για float *)
(* Μπορούμε να ορίζουμε δικούς μας τελεστές ή να ξανα-ορίσουμε υπάρχοντες.
Σε αντίθεση με την SML ή τη Haskell, μόνο ορισμένα σύμβολα μπορούν να
χρησιμοποιηθούν για ονόματα τελεστών και το πρώτο σύμβολο ορίζει την
επιμεριστικότητα και προτεραιότητα πράξεων. *)
let (+) a b = a - b ;; (* και καλή τύχη στον επόμενο... *)
(* Πιο χρήσιμο: ένας τελεστής αντιστρόφου για floats.
οι τελεστές ενός-ορίσματος πρέπει να ξεκινούν με "~". *)
let (~/) x = 1.0 /. x ;;
~/4.0 (* = 0.25 *)
(*** Built-in δομές δεδομένων ***)
(* Οι λίστες περικλείονται από αγκύλες και τα στοιχεία τους
διαχωρίζονται με semicolons. *)
let my_list = [1; 2; 3] ;;
(* Οι tuples (προαιρετικά) περικλείονται από παρενθέσεις, τα στοιχεία τους
διαχωρίζονται με κόμματα. *)
let first_tuple = 3, 4 ;; (* Έχει τύπο "int * int". *)
let second_tuple = (4, 5) ;;
(* Συνέπεια: αν προσπαθήσεουμε να διαχωρίσουμε τα στοιχεία μιας λίστας
με κόμματα, θα πάρουμε μια λίστα με ένα tuple ως στοιχείο.
Μπορεί να την πατήσουμε εύκολα έτσι. *)
let bad_list = [1, 2] ;; (* Becomes [(1, 2)] *)
(* Μπρούμε να προσπελάσουμε στοιχεία μιας λίστας με τη συνάρτηση List.nth. *)
List.nth my_list 1 ;;
(* Yπάρχουν συναρτήσεις ανώτερης τάξης για λίστες, όπως οι map και filter. *)
List.map (fun x -> x * 2) [1; 2; 3] ;;
List.filter (fun x -> x mod 2 = 0) [1; 2; 3; 4] ;;
(* Μπορούμε να προσθέτουμε στοιχεία στην αρχή μιας λίστας με τον
constructor "::", συνήθως αναφέρεται ως "cons". *)
1 :: [2; 3] ;; (* Αποτέλεσμα: [1; 2; 3] *)
(* Οι πίνακες Arrays περικλείονται από [| |] *)
let my_array = [| 1; 2; 3 |] ;;
(* Προσπελαύνουμε στοιχεία ενός πίνακα ως εξής: *)
my_array.(0) ;;
(*** Strings και Χαρακτήρες ***)
(* Χρησιμοποιούμε διπλά εισαγωγικά για τα string literals. *)
let my_str = "Hello world" ;;
(* Μονά εισαγωγικά για τα literals χαρακτήρων. *)
let my_char = 'a' ;;
(* Τα μονά και τα διπλά εισαγωγικά δεν είναι ισοδύναμα. *)
let bad_str = 'syntax error' ;; (* Syntax error. *)
(* Αυτό μας δίνει ένα string με έναν χαρακτήρα και όχι εναν χαρακτήρα. *)
let single_char_str = "w" ;;
(* Τα strings παρατίθενται με τον τελεστή "^". *)
let some_str = "hello" ^ "world" ;;
(* Τα strings δεν είναι πίνακες από χαρακτήρες όπως στην C.
Δεν μπορούμε να ανακατεύουμε strings με χαρακτήρες σε εκφράσεις.
Μπορούμε να μετατρέπουμε χαρακτήρες σε strings με "String.make 1 my_char".
Υπάρχουν πιο βολικές συναρτήσεις για αυτό το σκοπό σε πρόσθετες βιβλιοθήκες,
όπως η Core.Std που μπορεί να μην έχουν εγκατασταθεί/φορτωθεί by default. *)
let ocaml = (String.make 1 'O') ^ "Caml" ;;
(* Υπάρχει και μια συνάρτηση printf. *)
Printf.printf "%d %s" 99 "bottles of beer" ;;
(* Υπάρχουν και συναρτήσεις read/write χωρίς μορφοποίηση. *)
print_string "hello world\n" ;;
print_endline "hello world" ;;
let line = read_line () ;;
(*** User-defined τύποι δεδομένων ***)
(* Μπορούμε να ορίζουμε τύπους δεδομένων με τη δομή "type some_type".
Όπως σε αυτό τον άχρηστο τύπο που αντιγράφει τους ακεραίους: *)
type my_int = int ;;
(* Πιο ενδιαφέροντες τύποι περιλαμβάνουν τους λεγόμενους type constructors.
Αυτοί πρέπει να ξεκινούν με κεφαλαίο γράμμα. *)
type ml = OCaml | StandardML ;;
let lang = OCaml ;; (* Έχει τύπο "ml". *)
(* Οι type constructors δε χρειάζεται να είναι κενοί. *)
type my_number = PlusInfinity | MinusInfinity | Real of float ;;
let r0 = Real (-3.4) ;; (* Έχει τύπο "my_number". *)
(* Μπορούν να χρησιμοποιηθούν για πολυμορφική αριθμιτική *)
type number = Int of int | Float of float ;;
(* Σημείο στο επίπεδο, βασικά ένα tuple περιορισμένου συγκεκριμένου τύπου *)
type point2d = Point of float * float ;;
let my_point = Point (2.0, 3.0) ;;
(* Οι τύποι μπορούν να είναι παραμετροποιημένοι, όπως σε αυτόν τον τύπο για
λίστες λίστών με οτιδήποτε τύπου στοιχεία. Το 'a μπορεί να αντικατασταθεί από
οποιονδήποτε τύπο. *)
type 'a list_of_lists = 'a list list ;;
type int_list_list = int list_of_lists ;;
(* Οι τύποι μπορούν επίσης να ορίζονται αναδρομικά. Σαν αυτόν εδώ τον τύπο που
είναι ανάλογος της built in λίστας από ακεραίους. *)
type my_int_list = EmptyList | IntList of int * my_int_list ;;
let l = IntList (1, EmptyList) ;;
(*** Ταίριασμα Προτύπων - Pattern Matching ***)
(* Το ταίριασμα προτύπων είναι κάπως σαν το switch statement σε προστακτικές
γλώσσες προγραμματισμού, αλλά παρέχει πολύ μεγαλύτερη εκφραστική ισχύ.
Παρόλο που φαίνεται περίπλοκο, στην πραγματικότητα είναι απλώς ταίριασμα
ενός ορίσματος με μια συγκεκριμένη τιμή, ένα κατηγόρημα ή έναν type constructor
Το σύστημα τύπων είναι αυτό που το κάνει τόσο ισχυρό. *)
(** Ταίριασμα με ακριβείς τιμές. **)
let is_zero x =
match x with
| 0 -> true
| _ -> false (* Το "_" σημαίνει "οτιδήποτε άλλο". *)
;;
(* Εναλλακτικά μπορούμε να χρησιμοποιούμε το keyword "function". *)
let is_one = function
| 1 -> true
| _ -> false
;;
(* Ταίριασμα με κατηγορήματα, γνωστό και ως "guarded pattern matching". *)
let abs x =
match x with
| x when x < 0 -> -x
| _ -> x
;;
abs 5 ;; (* 5 *)
abs (-5) ;; (* 5 πάλι *)
(** Ταίριασμα με type constructors **)
type animal = Dog of string | Cat of string ;;
let say x =
match x with
| Dog x -> x ^ " says woof"
| Cat x -> x ^ " says meow"
;;
say (Cat "Fluffy") ;; (* "Fluffy says meow". *)
(** Διάσχιση δομών δεδομένων με ταίριασμα προτύπων **)
(* Οι αναδρομικοί τύποι μπορούν να διασχιστούν εύκολα με ταίριασμα προτύπων.
Ας δούμε πώς μπορούμε να διασχίσουμε μια λίστα.
Παρόλο που το built-in cons ("::") μοιάζει με infix τελεστή,
στην πραγματικότητα είναι ένας type constructor και μπορεί να
ταιριαστεί όπως όλοι οι type constructors. *)
let rec sum_list l =
match l with
| [] -> 0
| head :: tail -> head + (sum_list tail)
;;
sum_list [1; 2; 3] ;; (* Αποτιμάται σε 6 *)
(* Η built-in συνταξη των cons εμποδίζει τη δομή λίγο, γι αυτό θα φτιάξουμε
το δικό μας τύπο λίστας για την παρουσίαση. *)
type int_list = Nil | Cons of int * int_list ;;
let rec sum_int_list l =
match l with
| Nil -> 0
| Cons (head, tail) -> head + (sum_int_list tail)
;;
let t = Cons (1, Cons (2, Cons (3, Nil))) ;;
sum_int_list t ;;
Έχετε κάποια πρόταση; Κάποια διόρθωση, ίσως; Δημιουργήστε ένα Issue στο GitHub Repo μας, ή ανοίξτε ένα pull request!
Αρχική συμβολή: Daniil Baturin, ενημερώσεις/επεξεργασία: 3 contributor(s).