Translation in Progress
The original version in french-language is still available here.

Notice d'utilisation

© 2005 2006 2007 2018 Florent Monnier
This file is provided under FDL license, which means that you can reuse it, with or without modifications as long as the copyright is preserved, and its license.
You can also use the CC-by-sa license if you prefer.
You are also invited to send me messages to suggest me improuvements, or to fix errors.

Introduction about OCaml

OCaml (also called Objective Caml) is a programming and scripting language. The goal of this document is to provide an introduction about the basic elements of this programming language.
(Don't try to contact me on this electronic mail anymore, I forgot the password.)
 
As explained on the website of this programming language, ocaml is supposed to be a programming language created by the INRIA.
It is advertised as a programming language with a safety of execution, and with strongly typed elements.
It is also described as a functional programming language, but not a pure one as Haskell.


Different Execution modes

OCaml provides several execution modes:
All these execution modes will be seen with more details later. In this tutorial, we first experiment with the top-level.

Different Programming Styles

We can adopt several programming styles with ocaml: functional, imperative, object, modular, and mix these different programming styles.
The functional style is supposed to be better when it's about to avoid side effects.
It's usually usefull to avoid a lot of global variables, that are difficult to track down.
It also has as advantage to make your functions easier to test.
The drawback is that it sometimes make the code more complicated, when we have to update or calculate several elements at the same times.
If you want to continue, then find an online interactive loop to begin, or even install it from: https://ocaml.org/docs/install.html
or you may install it from the installer provided with your computer or the device you're using.

The interactive loop

To begin, one of the first step is to get used to the type system of ocaml which is a quite strong type system. It might feel constraining at the beginning but it could be of some help, while using it.
Then you can use the interactive loop, called the "toplevel", with the 'ocaml' command in the console. You should then get a new session with a prompt similar than this one:
[guest@localhost ˜]$ ocaml
        OCaml version 4.12.2

#
Every expression should be followed by two ';':
;;
In order to understand the type system, we will see the primitive types first, they are the base elements:
It's also possible to make it easier to make edits in the interactive loop with the rlwrap tool, like this:
[guest@localhost ˜]$ rlwrap ocaml
      OCaml version 4.12.2

#
This tool offers a read-line interface, which adds edits capabilities in the interactive loop.

Basic Types

1) The integer type (integer number), try to type in the prompt:
# 4 ;;
The interactive loop will then reply with the type of the expression that the type system inferred (guess), followed by its value:
- : int = 4
2) floating point numbers (float's):
# 8.6 ;;
- : float = 8.6
The dot is required so that the type system knows it's a float, even if there is nothing after:
# 12. ;;
- : float = 12.
Otherwise we would get an integer:
# 12 ;;
- : int = 12
3) The boolean type, which value is either true or false:
# true ;;
- : bool = true
# false ;;
- : bool = false
4) The character type (one, and only one character.) They are provided between single quotes:
# 'e' ;;
- : char = 'e'
5) The string type, contains zero, or several characters. They are provided between double quotes:
# "OCaml" ;;
- : string = "OCaml"
The concatenation operator for strings is:
# "OC" ^ "aml";;
- : string = "OCaml"
You can access to any character of a string like this:
# "OCaml".[2] ;;
- : char = 'a'
As you can see, the type of the element returned is the character type char as you would expect. The index starts at 0 (zero) for the first character, and the last one is n-1, with n been the number of chars in the string. (if the string contains some utf-8 special characters, n might be different than what you expect, because special characters may take 2 bytes/octets.)
If we try to access to a char beyond the last one, an exception will be raised:
# "OCaml".[26] ;;
Exception: Invalid_argument "index out of bounds".
Defining, raising and catching exceptions will be seen below.
6) The type « unit » :
# () ;;
- : unit = ()
This type is a kind of empty entity. It is used for example when a function doesn't need any parameters. This is also the default returned type when a function does something but doesn't have anything to return. This type is not exactly the same than « void » and « NULL » of other languages, because its use is not exactly the same.
A function that doesn't have anything to return, will return a single « unit » :
# print_endline "OCaml" ;;
OCaml
- : unit = ()
Here the function does something (displaying text, which is considered as a "side effect"), but doesn't have anything to return, so the default returned value is a single "unit".

Transtyping

OCaml features a strong type system, because it is believed that a statically strong type system, is a great help, in order to avoid programming errors. OCaml will not do any "implicit type coercion", because it is believed that it can lead to incidious errors, so ocaml programmers have to do it explicitly. ("type coercion" is converting a type to another compatible type, like for example converting an integer number into a float number, or the opposite.)
To convert an integer number into a floatting point number, you can do:
# float_of_int 16 ;;
- : float = 16.
And the opposite conversion:
# int_of_float 12.0 ;;
- : int = 12
If you want to get the ascii-code (character encoding) of a character, you can do:
# int_of_char 'A' ;;
- : int = 65
And the reverse:
# char_of_int 97 ;;
- : char = 'a'
In a similar way, fr arithmetic operations, the numbers have to be of the same type:
# 4 + 6 ;;
- : int = 10
For arithmetic operations with floats, operators have to be suffixed with a dot:
# 2.4 *. 1.2 ;;
- : float = 2.88
You can mix both using a coercion from a type to another:
# (float_of_int 9) /. 1.2 ;;
- : float = 7.5

In order to define a "named-value" (some data, or a function), we can use the let construct follow by a name that will identify the defined value:
# let my_item = 128 ;;
val my_item : int = 128
Here the element that we define is an integer number, and it will be possible to use it everywhere after its definition (where an integer type is expected), using the name that we used to define it. The ocaml top-level, after this definition, will reply with this name, and its associated value, and its infered type. (This name can not start with an upper-case character, or a number.)
You can check that the link to the value has properly being defined to its value by calling its name:
# my_item ;;
- : int = 128
Most often, the word "variable" is avoided, because ocaml is a functional programming language, and functional programming languages try to avoid mutability, so the variable should not vary. But ocaml is an "impure" functional programming language, so it is possible to introduce mutable elements. It is possible to use the terms: "mutable named-value", in this case in stead of "variable". It is often easier to use the same word than in other programming languages ("variable") though.
# let my_item = my_item * 2 ;;
val my_item : int = 256
It is also possible to redefine another "named-value" with the same name, this is called: "shadowing", because the previous definition is hidden by the new one.
Les variables modifiables transversalement seront vues ultérieurement, mais si vous faites le choix d'adopter un style fonctionel vous éviterez leur usage ; le fait que leur valeur soit accessible transversalement induit des effets de bords, souvent à l'origine de bugs. Il est pratiquement toujours possible d'utiliser une valeur nommée fonctionnelle plutôt qu'une variable impérative.
Après pour l'écriture de scripts, et dans certains cas un style impératif local à une fonction permet parfois d'écrire du code plus simple à lire et à écrire. Il ne faut pas être trop extrémiste non plus, la simplicité du code étant souvent plus importante.

OCaml fournit plusieurs structures permettant de regrouper des éléments (comme les types primitifs vu en début du didacticiel) : les tableaux (type 'array'), les listes (type 'list') et les tuples.

Arrays

Arrays can group together, zero, one, or several elements of the same type. The number of elements in ocaml arrays can not change.
# [| 'b'; 'E'; 't'; 'i' |] ;;
- : char array = [|'b'; 'E'; 't'; 'i'|]

# let my_array = [| 1; 5; 7; 12; 64 |] ;;
val my_array : int array = [|1; 5; 7; 12; 64|]
Arrays are the right data structure to access any of their element, in constant time, from an index:
# my_array.(2) ;;
- : int = 7
L'accès à n'importe quel élément d'un tableau se fait en vitesse constante The time that will take the access of one element, is quite small, and is always the same for every elements of the array. This is what we call "constant-time", and in computer complexity-study it is written O(1) (pronouced "Oh, one"), and this is not the same for lists, where to access one of its element, we have to pop all the previous elements. This is why with lists, we say that the time needed to access an element, is O(n), where n is the position of the element in the list.

Lists

OCaml lists should also contain only elements of the same kind/type:
# [ 1; 3; 45; 24; 17; 0; 9 ] ;;
- : int list = [1; 3; 45; 24; 17; 0; 9]
On réalise l'empilement et le dépilement des éléments uniquement au sommet de la pile. Les listes ne sont donc pas de taille fixe comme les tableaux.
# let my_list = [ 3; 4; 5 ] ;;
val my_list : int list = [3; 4; 5]

# 8 :: my_list ;;
- : int list = [8; 3; 4; 5]
Elle sont bien adaptées pour réaliser des piles d'éléments, dans lesquelles on va venir empiler et dépiler des éléments au sommet de la pile. (Pensez pour cela à une pile d'assiette, au sommet de laquelle on vient ajouter les assiettes lavées, puis au sommet de laquelle on va venir prendre des assiettes propres lorsqu'on en a besoin.)
L'empilement et le dépilement d'éléments peuvent être réalisés respectivement à partir et jusqu'à la liste vide :
# [] ;;
- : 'a list = []
(Le « 'a » indique alors que le type est indéfini.)
Les listes sont bien adaptées lorsque ses éléments doivent être parcourus de manière séquentielle.

Tuples

Les tuples permettent de regrouper des éléments qui peuvent être de type différent. Ils regroupent généralement un petit nombre d'éléments. Il sont utilisés par exemple lorsqu'une fonction doit retourner plusieurs éléments (qui seront alors regroupés dans un tuple).
Tuples can group elements that can be of several different types. They usually group a small number of elements together. They are used for example when a function can return several elements.
# (3, 'F') ;;
- : int * char = (3, 'F')

# let a = 3 ;;
val a : int = 3
# let b = 'F' ;;
val b : char = 'F'

# let (a, b) = (b, a) ;;
val a : char = 'F'
val b : int = 3

# a ;;
- : char = 'F'
# b ;;
- : int = 3
Tuples can contain anything, numbers, chars, strings, lists, arrays, and even functions!
# (12.5, "www.ifrc.org", float_of_int) ;;
- : float * string * (int -> float) = (12.5, "www.ifrc.org", <fun>)
Et comme nous l'avons dit, en OCaml tout a un type, même les fonctions ont leur type. Nous allons voir ça plus bas.
Le fait que les fonctions puissent ĂȘtre placée dans des structures et passées en paramètre de fonction, on dit que les fonctions sont des éléments de première classe ("first class citizen functions" en anglais).

Functions

L'environnement OCaml fournit par défaut un ensemble de fonctions contenues dans sa librairie standard, composée de plusieurs "modules". Le nom des modules commence toujours par une majuscule, par exemple le module String qui fournit des fonctions pour manipuler les chaînes de caractères. Une des fonctions fournies par le module String est la fonction 'length'. Pour pouvoir l'utiliser il faut soit faire précéder le nom de la fonction par le nom du module, soit ouvrir le module 'String' au préalable. Voici les deux méthodes :
# String.length "OCaml" ;;
- : int = 5

# open String ;;
# length "OCaml" ;;
- : int = 5
Once opened, the functions of a module are accessible in the current repl session, or source code file, or code block.
OCaml est un langage à expression. Chaque élément est une expression, et chaque expression a un type. Ceci s'expérimente ainsi en tapant juste le nom d'une fonction, le type de la fonction est alors indiqué :
# length ;;
- : string -> int = <fun>
Le type d'une fonction est en quelque sorte ce que l'on appelle le prototype d'une fonction dans d'autres langages, mais ce n'est pas totalement équivalent en raison des possibilités d'application partielle d'OCaml qui seront vues ultérieurement.
Ici on voit que la fonction 'length' prend un argument de type 'string' (chaine de caractère) et qu'elle renvoie un élément de type 'int' (entier).
OCaml étant fortement typé, il est ainsi utile de vérifier le type d'une fonction avant de l'utiliser, afin de bien avoir en tête le type des paramètres à lui passer, et le type du ou des éléments ou de l'expression de retour de la fonction. On peut ainsi vérifier le type des fonctions utilisées au tout début de cette introduction à OCaml :
# float_of_int ;;
- : int -> float = <fun>
# int_of_char ;;
- : char -> int = <fun>
# print_string ;;
- : string -> unit = <fun>
Ces fonctions font partie du module 'Stdlib' qui est le module ouvert par défaut (il n'y a pas besoin de l'ouvrir pour en utiliser ses fonctions).
Si vous souhaitez utiliser une ancienne version d'OCaml le module 'Stdlib' se nommait 'Pervasives' avant.
Nous avons également évoqué le module String juste avant, chaque module a une page de documentation dans le manuel d'OCaml.
Cependant pour utiliser certaines de ces fonctions, vous aurez probablement besoin d'en connaître un petit peu plus sur OCaml avant, donc lisez la suite.

Pour définir une fonction nommée on utilise le constructeur 'let', comme pour définir une valeur :
# let step a = a + 2 ;;
val step : int -> int = <fun>

# step 6 ;;
- : int = 8
OCaml étant un langage à expression il n'est point besoin d'utiliser une instruction 'return' comme dans d'autres langages, on donne directement l'expression qui doit être renvoyée.

Dans un programme ou un script, nous avons souvent besoin de pouvoir exécuter certaines parties de codes plutôt que d'autres. OCaml offre plusieurs structures de contrôle pour cela.
(Si vous connaissez déjà des langages de programmation ou de script, vous serez peut-être un peu dépaysé par celles d'OCaml.)

Condition IF

La façon la plus simple d'utiliser la structure 'if' ("si" en français), est de lui fournir une comparaison de ce genre :
# 2 > 3 ;;
- : bool = false
ici 2 n'est pas supérieur à 3 donc le booléen 'false' est retourné.
(Pour l'égalité utilisez de préférence « = » à « == » sauf si vous êtes sûr que vous recherchez strictement l'égalité physique de la zone de RAM pointée.)
La structure de contrôle 'if' doit obligatoirement utiliser le type booléen (bool) :
# if 2 > 3 then "first case" else "second case" ;;
- : string = "second case"
On peut développer l'expression précédente :
# let condition = (2 > 3) ;;
val condition : bool = false

# if condition
  then "la condition est vraie"
  else "la condition est fausse"
  ;;
- : string = "la condition est fausse"
Comme les fonctions qui n'utilisent pas d'instruction return, cette structure de contrôle renvoie directement une expression, qui peut être utilisée par exemple pour définir une valeur nommée :
# let lang = "OCaml" ;;
val lang : string = "OCaml"

# let my_item =
    if lang.[0] = 'O'
    then "Objective"
    else "Unknown"
  ;;
val my_item : string = "Objective"
Enchainement de structures if ("forêts d'else if") :
# let comp a =
    if a = 4
    then "equal"
    else if a > 4
      then "sup"
      else "inf"
  ;;
val comp : int -> string = <fun>

# comp 6 ;;
- : string = "sup"
Afin de garantir le type retourné par la structure if, ses deux branches then et else doivent impérativement retourner des éléments de même type (ce qui est donc le type de l'ensemble de l'expression).
Le seul cas où il n'y a pas besoin de spécifier la seconde branche else c'est lorsque la première branche est de type « unit ».
# let is_equal_to_5 this =
    if this = 5
    then print_endline "this is equal to 5"
  ;;
val is_equal_to_5 : int -> unit = <fun>

# is_equal_to_5  3 ;;
- : unit = ()

# is_equal_to_5  5 ;;
this is equal to 5
- : unit = ()

Loops

For loops

# for var = 2 to 6 do
    print_endline ("var value is " ^ (string_of_int var))
  done
  ;;
var value is 2
var value is 3
var value is 4
var value is 5
var value is 6
- : unit = ()
Ici nous voyons apparaître comme type de retours pour l'ensemble de l'expression le type « unit ». En effet la structure de contrôle for correspond au type de programmation impérative, et elle ne peut contenir que des éléments dont la valeur de retours est le type « unit ».
Pour placer plusieurs instructions à l'intérieur d'une boucle for, utilisez le séparateur point virgule ';' entre chaque instruction comme ceci :
# for var = 2 to 6 do
    print_string "var value is " ;
    print_int var ;
    print_newline ()
  done
  ;;
var value is 2
var value is 3
var value is 4
var value is 5
var value is 6
- : unit = ()
Pour éviter le style impératif de cette structure de contrôle, OCaml fournit une alternative offrant beaucoup plus de possibilités, et surtout une sureté très largement accrue.

Loops with functions

It's possible to create loops with functions calling themself.
We need to add the keyword 'rec' (which stands for recursive) when we define these functions.
# let rec loop a =
    print_int a;
    print_newline ();
    if a > 0 then
      loop (a - 1)
  ;;
val loop : int -> unit = <fun>

# loop 5 ;;
5
4
3
2
1
0
- : unit = ()
Le grand danger cependant avec des boucles réalisées de cette manière est d'en écrire une qui ne s'arrêtent jamais.
Il faut donc prêter un soin tout particulier à la condition d'arrêt.
Dans l'exemple ci-dessus, la fonction s'appelle elle-même avec un argument sans cesse inférieur, il nous est donc possible de réaliser le rebouclage que lorsque l'argument est supérieur à une certaine valeur, puisqu'il descendra forcément à un moment en dessous de celle-ci.

Match

Dans son utilisation basique la plus rudimentaire, cette structure de contrôle pourrait être vue comme un équivalent de la structure de contrôle « switch » que l'on connait dans d'autres langages de programmation et de script.
(À ceci prêt que « match » permet d'aller bien au delà.)
Here is a simple example:
# let var = 2 ;;
val var : int = 2

# match var with
  | 1 -> "un"
  | 2 -> "deux"
  | 3 -> "trois"
  | _ -> "je ne sais pas compter au delà de trois"
  ;;
- : string = "deux"
Ici la valeur de var est 2, l'élément correspondant après la flèche est donc retourné.
L'englobement du « match » doit toujours être exhaustif, d'une part, et toutes les expressions suivant chaque englobement doivent être de même type. Ce type sera ainsi le type de l'ensemble de l'expression « match ». (encore cette fameuse histoire de typage fort destiné à assurer le bon fonctionnement de votre code).
Le dernier englobement « _ » est utilisé si aucune des entrées le précédant ne correspond.
(L'englobement « _ » est à peu près équivalent de la directive « default » des structures 'switch' d'autres langages.)
L'englobement peut parfois être exhaustif sans celui-ci (voir l'exemple à la fin).
Précédemment nous avions vu comment empiler un élément à une liste. Voici comment en dépiler un :
# let li = [ 2; 8; 5; 11; 32 ] ;;
val li : int list = [2; 8; 5; 11; 32]

# match li with
  | head :: tail -> tail
  | [] -> []
  ;;
- : int list = [8; 5; 11; 32]
(* Dans le jargon, on désigne le premier élément d'une liste comme
   sa tête (head en anglais), et le reste comme sa suite (tail).
   Ici l'expression renvoie la suite de la liste nommée « li ». *)

# match li with
  | head :: tail ->
      print_string "the head is ";
      print_int head;
      print_newline ();
  | [] ->
      print_endline "the list is empty";
  ;;
the head is 2
- : unit = ()
(ici on voit aussi apparaître du commentaire entre les séquences '(*' et '*)')
En utilisant cette possibilité de désempilement au sein d'une boucle faite avec une fonction récursive, cela permet alors de parcourir tous les éléments d'une liste.
# let li = [2; 3; 5; 7] in
  let rec loop li =
    match li with
    | head :: tail ->
        print_int head;
        print_newline ();
        loop tail;
    | [] ->
        print_endline "End Of List";
  in
  loop li
  ;;
2
3
5
7
End Of List
- : unit = ()

"Foreach"

(Iteration sur les listes et les tableaux)

Si vous connaissez d'autres langages et que vous recherchez un équivalent de la structure foreach, la même chose peut-être réalisée pour parcourir tous les éléments d'une liste avec les fonctions « iter » (impératif) et « map » (fonctionnel) du module 'List' de la librairie standard d'OCaml.
# let for_each item = item * 2 ;;
val for_each : int -> int = <fun>

# let my_list = [ 5; 1; 22; 3; 16 ] ;;
val my_list : int list = [5; 1; 22; 3; 16]

# List.map  for_each  my_list ;;
- : int list = [10; 2; 44; 6; 32]
Ici la fonction for_each est appliquée successivement à chaque élément de la liste my_list.
Ainsi la fonction prend en argument un des éléments de la liste (qui doit donc avoir le même type que l'arguement de la fonction), et les résultats successifs de la fonction sont empilés dans une nouvelle liste qui est le résultat global de l'appel à List.map for_each my_list. Cette opération fonctionne donc comme un filtre.
Si la fonction for_each n'est utilisée qu'à un seul endroit du code, elle peut être définie localement au sein de l'expression en cours, plutôt que globalement. (On pourrait tenter la comparaison avec les fonctions inline de C++) Il faut alors la terminer par 'in' à la place de ';;' comme ceci :
# let each item =
    item + 4
  in
  List.map each [10; 50; 30; 80; 20]
  ;;
- : int list = [14; 54; 34; 84; 24]
Elle ne sera alors disponible et accessible que dans l'expression en cours, comme vous pouvez le vérifier :
# each ;;
Unbound value each
Une fonction anonyme peut aussi être utilisée :
# List.iter
    (fun i -> print_int i ; print_newline ())
    [ 8; 64; 2; 16 ]
  ;;
8
64
2
16
- : unit = ()
 
# let high_level_languages = [
    "LISP";
    "Perl";
    "Python";
    "PHP";
    "Ruby";
    "OCaml";
    "Nim"
  ] in
  List.iter (fun lang -> print_endline lang) high_level_languages
  ;;
LISP
Perl
Python
PHP
Ruby
OCaml
Nim
- : unit = ()
Pour parcourir les éléments d'un tableau au lieu d'une liste, il existe deux fonctions semblables « iter » et « map » dans le module 'Array'.

Using Records

OCaml records are data structures that should be defined before to be used.
This data structure is similar than tuples, except than every field can be accessed with a name (the field-name).
The OCaml programming language provides the record data-structure to group several elements of different type kinds.
They need to be defined, (with the key-word type), before they can be used.
The definition, defines every field, with a field name, and its type kind. .
# type person = { name: string; telephone: string; } ;;
type person = { name : string; telephone : string; }

# let doe = {
    name = "Doe";
    telephone = "FD.CE.AF";
} ;;
val doe : person = {name = "Doe"; telephone = "FD.CE.AF"; }

# doe.telephone ;;
- : string = "FD.CE.AF"

Les Enregistrements : Mise à Jour Immuable

(ou "Immutable Update" en anglais)
En programmation fonctionnelle les variables ne sont pas sensées changer de valeur, pour obtenir un nouvel enregistrement avec un des champs mis à jour, en OCaml nous procédons comme suit :
# let doe = { doe with telephone = "C85.5C8" } ;;
val doe : person = {nom = "Doe"; telephone = "C85.5C8"}

Les Variants

Les variants sont également des types qui doivent être déclarés avec une définition.
Voici un exemple librement inspiré d'un exemple de David MENTRE :
# type chocolat = Chocolat_noir | Chocolat_blanc | Chocolat_au_lait ;;
type chocolat = Chocolat_noir | Chocolat_blanc | Chocolat_au_lait

# let quoi_en_faire tablette =
    match tablette with
    | Chocolat_noir    -> "Je le mange goulûment !"
    | Chocolat_au_lait -> "Je le refile à ma petite soeur."
    | Chocolat_blanc   -> "Je le refile à mon voisin..."
  ;;
val quoi_en_faire : chocolat -> string = <fun>

# quoi_en_faire Chocolat_noir ;;
- : string = "Je le mange goulûment !"

Mise en garde contre les tableaux

Attention les tableaux contrairement aux listes sont des structures de type impérative dont les éléments sont modifiés en place, et donc avec ce que l'on appel des effets de bord. Si une fonction en modifie un élément, cette modification prendra effet transversalement, comme le montre cet exemple :
# let arr = [| 1; 2; 3; 4; 5 |] ;;
val arr : int array = [|1; 2; 3; 4; 5|]

# let modif i v =
    arr.(i) <- v;
  ;;
val modif : int -> int -> unit = <fun>

# modif 2 1024 ;;
- : unit = ()

# arr ;;
- : int array = [|1; 2; 1024; 4; 5|]
Ainsi si une fonction modifie un tableau avant de s'en servir pour quelqu'usage que ce soit, celui-ci ne sera pas intact après l'appel à cette fonction. Il faut y penser et y être attentif pour en tenir compte.
Ce type de problème n'existe pas avec les listes qui sont des structures de type fonctionnel.
Le même type d'effet de bord peuvent avoir lieu avec les chaînes de caractères :
# let str = "OCaml" ;;
val str : string = "OCaml"

# let modif str i c =
    str.[i] <- c;
  ;;
val modif : string -> int -> char -> unit = <fun>

# modif str 1 'k' ;;
- : unit = ()

# str ;;
- : string = "Okaml"
Ainsi si une fonction a besoin d'utiliser une version modifiée d'un tableau ou d'une chaîne de caractère et que le programme en cours aura encore besoin de leur version originale après l'appel de fonction en question, vous pouvez utiliser une copie locale à la place de l'originale avec les fonctions String.copy ou Array.copy.

Terminal Recursion

La récursivité terminale est une notion très importante à maîtriser en OCaml, car de sa maîtrise dépend la scalabilité de vos programmes.
Voici un exemple de fonction récursive qui calcule la somme d'une suite d'entier de 1 jusqu'à un nombre donné :
# let rec add x =
    if x <= 0
    then 0
    else x + add (x - 1)
  ;;
val add : int -> int = <fun>
À priori, cette fonction paraît correcte, sauf que pour des valeurs élevées il risque d'y avoir une surcharge qui empèchera à cette fonction de renvoyer le résultat.
#  add 10 ;;
- : int = 55

# add 100 ;;
- : int = 5050

# add 10_000 ;;
- : int = 50005000

# add 100_000 ;;
Stack overflow during evaluation (looping recursion?).
Voici ce qui se passe, à la dernière ligne de la fonction, à la suite du else, il y a une addition avec le résultat d'un appel de fonction, donc le calcul ne peut être réalisé aussitôt car il faut d'abord calculer le résultat de cette fonction, le calcul est donc laissé en suspend en attendant. Or il y aurra autant de calcul laissés en suspend que d'appel total à la fonction, ce nombre correspondant ici à la valeur du paramètre.
Donc pour cent mille, il y aurra cent mille appels successifs à la fonction et donc cent mille calculs laissés en instance !
La pile d'appel des fonctions (stack) est alors surchargée (overflow).
C'est un peu comme une personne qui aurait du travail à faire et qui chaque jour le remettrait au lendemain, et à la fin il se retrouverait incapable de réaliser tout le travail accumulé.
La solution est donc de réaliser le calcul à faire à chaque étape et de passer le résultat à la boucle suivante.
Il faut donc réaliser une fonction auxiliaire 'aux' avec un paramètre additionnel 'acc' où le résultat final s'accumule à chaque boucle :
# let addt x =
    let rec aux x acc =
      if x <= 0
      then acc
      else aux (x - 1) (x + acc)
    in
    aux x 0
  ;;
val addt : int -> int = <fun>

# addt 100_000 ;;
- : int = 705082704
La nouvelle fonction est alors dite "récursive terminale", car dans la récursivité le résultat est donné en position terminale, ici au niveau du 'then', alors qu'avant c'était le bout de la chaîne le résultat étant situé au niveau de l'addition (lors de la dernière boucle).
Dans la première version de la fonction, le then renvoyait un 0, c'est donc avec cette valeur que la variable acc est initialisée lors de l'appel à la fonction auxiliaire aux.

Voici un autre exemple de fonction récursive non terminale. Cette fonction produit une liste d'entiers entre deux entiers donnés :
# let rec up_to m n =
    if m > n
      then up_to n m
      else if m = n
        then [m]
        else m :: up_to (m+1) n
    ;;
val up_to : int -> int -> int list = <fun>

# up_to 4 12;;
- : int list = [4; 5; 6; 7; 8; 9; 10; 11; 12]
Et voici la version récursive terminale :
# let up_to m n =
  let rec aux m n acc =
    if m > n
    then acc
    else aux m (n-1) (n::acc)
  in
  if m > n
  then aux n m []
  else aux m n []
  ;;
val up_to : int -> int -> int list = <fun>

# up_to 4 12 ;;
- : int list = [4; 5; 6; 7; 8; 9; 10; 11; 12]
On peut vérifier la capacité de ces deux fonctions avec
ignore(up_to 0 100000) ;;
la version récursive terminale est effectivement plus performante.
D'après le manuel d'OCaml, il faut utiliser une version récursive terminale dès lors que le nombre d'éléments des listes à traiter peut dépasser les dix milles (environ).

Script et exécutables

Après avoir utilisé le top-level, maintenant voyons comment créer des scripts et des exécutables. Pour celà ouvrez un nouveau fichier avec l'extension de fichier .ml correspondant aux fichiers source OCaml, et écrivez une simple instruction, comme par exemple :
print_endline "Hello World!" ;;
Pour exécuter ce fichier en tant que script, il suffit ensuite de lancer l'interpréteur en ligne de commande avec le script en argument comme ceci :
$ ocaml hello.ml
Si vous utilisez un système de type Unix comme Linux, MacOS ou Cygwin, il vous est également possible de pouvoir exécuter votre script comme un exécutable sans préciser l'interpréteur sur la ligne de commande. Voici comment procéder, premièrement ajoutez à la première ligne du script le chemin de l'interpréteur comme ceci :
#!/usr/bin/env ocaml
ensuite rendez le exécutable en lui ajoutant les droits d'exécution :
chmod a+x hello.ml
vous pouvez alors exécuter directement votre script comme n'importe quel exécutable :
./hello.ml
Et pour ce qui est de créer des exécutables pour de meilleures performences, vous pouvez compiler votre fichier source au choix en code natif ou en byte-code. La compilation en byte-code a pour avantage que l'exécutable pourra être exécuté indépendament sur n'importe quel système d'exploitation où la machine virtuelle OCaml est installée. La machine virtuelle est un interpréteur qui interprète le code binaire dans lequel a été compilé le fichier source.
La compilation en code natif a elle pour avantage des performences optimums (comparable à du C ou du C++).
Ainsi pour compiler en byte-code, utilisez le compilateur correspondant :
ocamlc  hello.ml  -o hello.bin
./hello.bin
Et de même pour compiler un exécutable en code natif :
ocamlopt  hello.ml  -o hello
./hello
Idem pour les utilisateurs de Windows nécessitant une extention de fichier :
ocamlopt hello.ml -o hello.exe
./hello.exe

Voilà pour un premier épisode, il est un peu long et peu amusant pour un début, mais il faux comprendre le typage des expressions avant de pouvoir aller plus loin vers des choses plus évoluées.
Dites-moi si vous l'avez apprécié ou pas, pour que je sache si je dois en faire d'autres ou pas.

Aller plus loin

Si vous aimez apprendre en faisant de petits jeux, vous pouvez vous rendre à cette adresse pour trouver de petits exemples de mimi-jeux en OCaml.
Vous pouvez lire cette excellente "Introduction au langage OCaml" par Maxence Guesdon sous licence CC-by-nc-sa.
cours d'introduction à ocaml par Emmanuel Hainry.
Divers exemples (avec équivalences dans d'autres langages) sur le site RosettaCode.org.
Wikibook sur ocaml par divers auteurs : 1 / 2 / 3.

Camellement vôtre !
GNU/FDL © 2005 2006 2007 2018 2025 Florent Monnier
 

sommaire
The Caml Language