בOCaml, שגיאות נעשות באמצעות exceptions שמוגדרים על ידי הטיפוס exn. הטיפוס שלו הוא variant מהצורה
type exn =
| Division by zero
| Failure of String
ניתן להוסיף שגיאות בצורה דינמית באמצעות המילה השמורה exception
exception Int_exception of int;;
ניתן לזרוק exceptions באמצעות המילה השמורה raise
let hd = fun l -> match l with
| [] -> raise(Int_exception 2)
| h::t -> h;;
תפיסת שגיאות עם try with
try 1/0 with
Int_exception i -> i
| Failure s -> 5
| Division_by_zero -> 0;;
אין פונקציה שלא מחזירה כלום ב OCaml יש טיפוס מסוג unit שהערך היחיד שלו הוא ( ). למשל
print_string("a\n");;
a
-: unit = ()
אם יש
print_string("a\n");5;;
a
-: int = 5;
בדיפולט, משתנים הם immutable ב OCaml כלומר לא ניתן לשנות את ערכם מבלי לייצר עותק חדש.
ישנם מספר מבני נתונים אימפרטיבים שכן תומכים בשינוי state.
הסינטקס שלהם הוא כמו רשימות אבל הגודל שלהם קבוע. כלומר זה מערך חד מימדי סטטי.
(* Define a vector of integers *)
let vector = [|1; 2; 3; 4; 5|]
(* Define a vector of integers *)
let vector = [|1; 2; 3; 4; 5|]
(* Accessing elements of the vector *)
let first_element = vector.(0) (* Access the first element (indexing starts from 0) *)
let third_element = vector.(2) (* Access the third element *)
(* Modifying elements of the vector *)
vector.(1) <- 10
(* Printing the modified vector *)
Array.iter (fun x -> print_int x; print_string " ") vector;
print_newline()
נרצה לכתוב את פונקציה שמקבלת וקטור ושני אינדקסים ומחליפה בין האינדקסים בוקטור המקורי
let swap v i j = let x = v.(i) in
(v(i) <- v.(j) ; v.(j) <- x);;
השורה let x = v.(i) מאפשרת לנו להחזיק משתנה זמני.
נסתכל על מבנה הנתונים הבא
type point = {x: float ; y: float}.
כעת נגדיר פונקציה שמוסיפה נתונים לנקודה
let add = fun ((dx, dy), p) -> {x = p.x +. dx; y =p.y +. dy}
ניתן לראות שהפונקציה שלנו מחזירה נקודה חדשה ולא מעדכנת את הנקודה שהבאנו כקלט.
אם נרצה להפוך אותו ל mutable נוכל לעשות את זה בקלות באופן הבא
type mpoint = {mutable x : float ; mutable y : float}
כעת נבנה פונקציה חדשה שדומה לadd אבל עובדת עם mutable point
let move = fun ((dx , dy), p) -> p.x <- p.x+dx ; p.y <- p.y + dy;;
נשים לב שאם נריץ את move נקבל שערך החזרה הוא unit בניגוד להרצה של add ששם ערך החזרה יהיה point .
בתכנות אימפרטיבי באוקמל כן יש תמיכה בלולאות

בOCaml יש
let mp1 = {x=1.0, y=1.0};;
let mp2 = {x=1.0, y=1.0};;
mp1=mp2
mp1==mp2
let mp3 = mp1;
mp3==mp1;
השיוויון הראשון יחזיר true והשני יחזיר false. לבסוף השיוויון השלישי יחזיר true בגלל שאלו ממש אותם אובייקטים.
references
ref הוא מילה שמורה שמגדירה reference. בדומה לשפת c , ניתן לעדכן את הערך של הref, ניתן לחלץ את הערך מהref וניתן לעדכן את הערך ששמור בתוך ה ref.
let c = ref 0;;
(** val c : int ref = {contents = 0};**)
c:= 5;; (** update the value in the reference **)
!c;; (*access the value of the reference*)
נוכל לפתח את זה לקונספטים יותר מעניינים למשל רשימה של ref
let l = [ref 1; ref 2; ref 3];;
(** var l: int ref list = [{...}]**)
(*lets try to extract the first element from the list*)
List.hd(l)
(** int ref = {contents = 1} **)
!List.hd(l);;
(** Error- OCaml will first try to search the memory address if the inerpreter sees '!', before evaluate the function **)
!(List.hd(l));;
(*** int = 1 *)
List.hd(l) := 10;;
(** will update the first element in the list to point to value 10 **)
אחד השימושים הנפוצים של רפרנסים באוקמל הוא בניית פונקציות שיש להן internal state. למשל נוכל להחזיק פונקציה שיש לה ref לאיזה פרמטר ובגלל שזה ref הוא לא ייעלם בין הקריאות לפונקציות.
let gensym = let counter = ref 0 in fun() -> counter := !counter + 1; "sym"^string_of_int(!counter);;
(** val gensym : unit -> string = <fun>**)
הפונקציה מחזירה את מה שבצד ימין (אחרי ה ; ) שזה שרשור של sym עם counter שעולה ב 1 בכל פעם שקוראים לפונקציה.
נשים לב שהאופרטור =: מייצג reference assignment והאופרטור ! מייצג הוצאת הערך מתוך ה ref.
שיוויון של מצביע
יכולה להיות בעיה בשיוויון מבני כאשר עובדים עם מבני נתונים שמאפשרים כפילויות בין אובייקטים שונים או במבני נתונים שמאפשרים מעגליות למשל ברשימה מקושרת מעגלית:
type 'a rlist =
| Nil
| Cons of 'a * ('a rlist ref);;
let newcell x y = Cons(x, ref y);;
let updnext (Cons (_,r)) y = r := y;;

אם נשווה את מבנה הנתונים הזה לפי מבנה נקבל לולאה אינסופית שכן OCaml ינסה לעבור על המבנה עד שהוא מגיע ל null ולהשוות תא תא, אבל אם תהיה רשימה מקושרת מעגלית הוא יתקע. רק אם נשווה בין כתובות נקבל true.

כדי להגדיר מודולים משתמשים במילים השמורות הבאות
מגדירים ראשית module type שזה דומה לinterfaces ברעיון.
module type LIST_STACK = sig
exception Empty
val empty : 'a list
val is_empty : 'a list -> bool
val push : 'a -> 'a list -> 'a list
val peek : 'a list -> 'a
val pop : 'a list -> 'a list
end
הפיצ׳ר הזה נקרא signatures.
באופן הזה אנחנו יכולים להפריד בין המימוש לבין ה״ממשק״ שמי שמשתמש במודול יכול לראות
module type LIST_STACK = sig
(** [Empty] is raised when an operation cannot be applied
to an empty stack. *)
exception Empty
(** [empty] is the empty stack. *)
val empty : 'a list
(** [is_empty s] is whether [s] is empty. *)
val is_empty : 'a list -> bool
(** [push x s] pushes [x] onto the top of [s]. *)
val push : 'a -> 'a list -> 'a list
(** [peek s] is the top element of [s].
Raises [Empty] if [s] is empty. *)
val peek : 'a list -> 'a
(** [pop s] is all but the top element of [s].
Raises [Empty] if [s] is empty. *)
val pop : 'a list -> 'a list
end
כעת נוכל לממש את המודול עם struct.
module ListStack : LIST_STACK = struct
let empty = []
let is_empty = function [] -> true | _ -> false
let push x s = x :: s
exception Empty
let peek = function
| [] -> raise Empty
| x :: _ -> x
let pop = function
| [] -> raise Empty
| _ :: s -> s
end
נשים לב שהמודול צמוד ל module type שהגדנו. לכן כל מימוש נוסף שנמצא בתוך ListStack לא יחשף מחוץ לmodule עצמו.
ניתן להגדיר מודולים בלי להצמיד להם type
אם הצמדנו בין מודול ל module type חובה לממש את כל הפונקציות בtype.
מודול אינו מחלקה (אין this) אלא רק אוסף של הגדרות.
הרחבת מודול
ניתן לקחת מודול קיים ולהוסיף לו פונקציות כרצוננו

עם הסינתקס include List יצרנו מעצם הרחב למודול של List. זה נקרא extensions, זה קונספט שיש אותו במספר שפות תכנות. במקרה הזה הפעולה לייצר מודול חדש שנקרא Extension.List ותוסיף את הפונקציה הזאת לשם.
