4. Exemple de notebook avec OCaml

4.1. Explications

Le kernel OCaml n’est pas installé par défaut avec Jupyter.

Il faut installer OPAM, puis ocaml-jupyter.

4.2. Exemples

Sys.command "ocaml -version";;
The OCaml toplevel, version 4.05.0
- : int = 0
print_endline "Bonjour depuis OCaml !";;
Bonjour depuis OCaml !
- : unit = ()

4.2.1. Une fonction récursive

Pour calculer la fonction \(n! := 1 \times 2 \times \dots \times n\) (\(n\in\mathbb{N}\)), on peut penser à une solution récursive (qui coutera en espace mémoire à cause de la pile d’appel) et une solution impérative.

let rec fact (n: int) : int =
    match n with
    | 0 -> 1
    | n -> n * (fact (n-1))
;;
val fact : int -> int = <fun>
for i = 1 to 21 do
    print_endline (Printf.sprintf "fact(%.2i) = %i" i (fact i))
done;;
fact(01) = 1
fact(02) = 2
fact(03) = 6
fact(04) = 24
fact(05) = 120
fact(06) = 720
fact(07) = 5040
fact(08) = 40320
fact(09) = 362880
fact(10) = 3628800
fact(11) = 39916800
fact(12) = 479001600
fact(13) = 6227020800
fact(14) = 87178291200
fact(15) = 1307674368000
fact(16) = 20922789888000
fact(17) = 355687428096000
fact(18) = 6402373705728000
fact(19) = 121645100408832000
fact(20) = 2432902008176640000
fact(21) = -4249290049419214848
- : unit = ()

Et la solution impérative :

let fact_imp (n: int) : int =
    let f = ref 1 in
    for i = 1 to n do
        f := (!f) * i;
    done;
    !f
;;
val fact_imp : int -> int = <fun>
for i = 15 to 21 do
    print_endline (Printf.sprintf "fact(%.2i) = %i" i (fact_imp i))
done;;
fact(15) = 1307674368000
fact(16) = 20922789888000
fact(17) = 355687428096000
fact(18) = 6402373705728000
fact(19) = 121645100408832000
fact(20) = 2432902008176640000
fact(21) = -4249290049419214848
- : unit = ()

Comme les entiers sont bornés, on dépasse la capacité assez rapidement, et les valeurs calculées deviennent fausses.

4.2.2. Un type non paramétrique récursif et un exemple :

type formulePropositionnelle =
    | Var of string
    | Non of formulePropositionnelle
    | Ou of (formulePropositionnelle * formulePropositionnelle)
    | Et of (formulePropositionnelle * formulePropositionnelle)
;;
type formulePropositionnelle =
    Var of string
  | Non of formulePropositionnelle
  | Ou of (formulePropositionnelle * formulePropositionnelle)
  | Et of (formulePropositionnelle * formulePropositionnelle)
let x = Var("x")
and y = Var("y")
and z = Var("z")
;;
val x : formulePropositionnelle = Var "x"
val y : formulePropositionnelle = Var "y"
val z : formulePropositionnelle = Var "z"
let p1 = Ou(x, y)
and p2 = Et(y, z)
and p3 = Non(x)
;;

let p4 = Et(p1, p2);;
let p5 = Ou(p3, p4);;
let p6 = Non(p5);;
val p1 : formulePropositionnelle = Ou (Var "x", Var "y")
val p2 : formulePropositionnelle = Et (Var "y", Var "z")
val p3 : formulePropositionnelle = Non (Var "x")
val p4 : formulePropositionnelle =
  Et (Ou (Var "x", Var "y"), Et (Var "y", Var "z"))
val p5 : formulePropositionnelle =
  Ou (Non (Var "x"), Et (Ou (Var "x", Var "y"), Et (Var "y", Var "z")))
val p6 : formulePropositionnelle =
  Non (Ou (Non (Var "x"), Et (Ou (Var "x", Var "y"), Et (Var "y", Var "z"))))
let rec taille (formule: formulePropositionnelle) : int =
    match formule with
    | Var _ -> 1
    | Non phi -> 1 + taille phi
    | Et (phi1, phi2) | Ou (phi1, phi2) -> 1 + (taille phi1) + (taille phi2)
    (* | _ -> 0 *) (* cette ligne est inutile *)
;;
val taille : formulePropositionnelle -> int = <fun>
taille x;;
taille y;;
taille z;;
- : int = 1
- : int = 1
- : int = 1
taille p1;;
taille p2;;
taille p3;;
- : int = 3
- : int = 3
- : int = 2

Avec une fonction récursive terminale :

let taille (formule: formulePropositionnelle) : int =
    let rec aux acc = function
        | Var _ -> 1
        | Non phi -> aux (acc+1) phi
        | Et (phi1, phi2) | Ou (phi1, phi2) -> aux (aux (acc + 1) phi1) phi2
    in aux 0 formule
;;
val taille : formulePropositionnelle -> int = <fun>
taille p4;;
taille p5;;
taille p6;;
- : int = 1
- : int = 1
- : int = 1

4.2.3. Un type paramétrique récursif et un exemple :

type 'a arbreBinaire = Feuille | Noeud of ('a arbreBinaire * 'a * 'a arbreBinaire);;
type 'a arbreBinaire =
    Feuille
  | Noeud of ('a arbreBinaire * 'a * 'a arbreBinaire)
let a0 = Feuille;;
(*
    1
  /   \
 F    F
*)
let a1 = Noeud(a0, 1, a0);;
(*
       2
     /   \
    1     F
  /   \
 F    F
*)
let a2 = Noeud(a1, 2, a0);;
val a0 : 'a arbreBinaire = Feuille
val a1 : int arbreBinaire = Noeud (Feuille, 1, Feuille)
val a2 : int arbreBinaire = Noeud (Noeud (Feuille, 1, Feuille), 2, Feuille)
let rec hauteur (arbre: 'a arbreBinaire) : int =
    match arbre with
    | Feuille -> 0
    | Noeud(g, _, d) -> 1 + max (hauteur g) (hauteur d)
;;
val hauteur : 'a arbreBinaire -> int = <fun>
hauteur a0;;
hauteur a1;;
hauteur a2;;
- : int = 0
- : int = 1
- : int = 2

4.2.4. Afficher des valeurs

print_string "Hello world from jupyter-ocaml";;
- : unit = ()
flush_all();;
- : unit = ()
print_endline "Hello world from jupyter-ocaml";;
Hello world from jupyter-ocamlHello world from jupyter-ocaml
- : unit = ()

Ce comportement est assez perturbant avec jupyter-ocaml, cf ce ticket.

print_endline "Hello world from jupyter-ocaml";;
Hello world from jupyter-ocaml
- : unit = ()

Avec Printf.printf, c’est encore plus facile :

let print = Printf.printf ;;
val print : ('a, out_channel, unit) format -> 'a = <fun>
print "Hello world from jupyter-ocaml\n%d + %d = %d\n" 1 2 (1 + 2);;
- : unit = ()
flush_all();;
Hello world from jupyter-ocaml
1 + 2 = 3
- : unit = ()

Avec une fonction autour de Printf.printf, c’est encore encore plus facile :

let print s =
    print_endline s ;
    flush_all ()
;;
val print : string -> unit = <fun>
for _ = 1 to 3 do
    print (Printf.sprintf "Hello world from jupyter-ocaml\n%d + %d = %d" 1 2 (1 + 2))
done;;
Hello world from jupyter-ocaml
1 + 2 = 3
Hello world from jupyter-ocaml
1 + 2 = 3
Hello world from jupyter-ocaml
1 + 2 = 3
- : unit = ()

4.2.5. Figures avec TikZ

Voir https://github.com/jbn/itikz

%load_ext itikz
File "[1]", line 1, characters 0-1:
Error: Syntax error
   1: %load_ext itikz

Il n’est pas encore possible d’utiliser des « magic commands » de IPython dans d’autres langages. J’ai ouvert ces tickets : #164 et #165 sur le projet ocaml-jupyter sur GitHub.

Une solution intermédiaire, et assez facile d’utilisation en fait, est de temporairement changer le « kernel » pour utiliser Python, générer les figures, puis repasser à OCaml !

%load_ext itikz
%%itikz --file-prefix tikz-figures-from-OCaml- --implicit-pic
\draw[help lines] grid (5, 5);
\draw[fill=magenta!10] (1, 1) rectangle (2, 2);
\draw[fill=magenta!10] (2, 1) rectangle (3, 2);
\draw[fill=magenta!10] (3, 1) rectangle (4, 2);
\draw[fill=magenta!10] (3, 2) rectangle (4, 3);
\draw[fill=magenta!10] (2, 3) rectangle (3, 4);
../_images/Exemple de notebook avec OCaml_44_0.svg
%%itikz --file-prefix tikz-figures-from-OCaml- --implicit-pic --scale=0.4
\tikzstyle{vertexcover} = [circle,fill=green,draw];
\tikzstyle{matching} = [ draw=blue!55, line width=5];
\tikzstyle{matchingN} = [ draw=green!55, line width=5];
\tikzstyle{vertex} = [circle,fill=none,draw];
\node[vertex] (v2) at (0,0) {};
\node[vertexcover] (v3) at (0,3) {};
\node[vertexcover] (v4) at (2,2) {};
\node[vertexcover] (v11) at (3,0) {};
\node[vertexcover] (v5) at (2,-2) {};
\node[vertexcover] (v6) at (1,-3) {};
\node[vertexcover] (v8) at (-1,-3) {};
\node[vertexcover] (v9) at (-2,-2) {};
\node[vertexcover] (v10) at (-3,0) {};
\node[vertexcover] (v1) at (-2,2) {};
\draw  (v1) edge (v2);
\draw  (v3) edge (v2);
\draw  (v4) edge (v2);
\draw  (v5) edge (v2);
\draw  (v6) edge (v2);
\draw  (v8) edge (v2);
\draw  (v9) edge (v2);
\draw  (v2) edge (v10);
\draw  (v2) edge (v11);
../_images/Exemple de notebook avec OCaml_45_0.svg
!ls tikz-figures-from-OCaml-*
tikz-figures-from-OCaml-c33720202396643581aed22f595383d4.svg
tikz-figures-from-OCaml-c33720202396643581aed22f595383d4.tex
tikz-figures-from-OCaml-f9a50e84b7fbf18e85525cc31a91a070.svg
tikz-figures-from-OCaml-f9a50e84b7fbf18e85525cc31a91a070.tex

4.2.6. IPython magic, en OCaml avec un hack ?

Les commandes IPython magic, comme utilisées ci-dessus (en basculant au kernel Python pour ces quelques cellules) sont très pratiques.

Un autre avantage sont les raccourcis, comme ceux là :

# cette cellule doit être exécutée avec le kernel Python
!ls *OCaml*nb
!pwd
'Exemple de notebook avec OCaml.ipynb'
/home/lilian/publis/Info-Prepas-MP2I/Modele-de-livre-avec-Jupyter-Book.git/notebooks

Pour certains commandes basiques, peut-être qu’on peut tricher un peu ?

let (! ) s =
    (* Sys.command ("echo "^s); *)
    Sys.command (s)
;;
val ( ! ) : string -> int = <fun>
!"ls *OCaml*nb";;
!"pwd";;
Exemple de notebook avec OCaml.ipynb
- : int = 0
/home/lilian/publis/Info-Prepas-MP2I/Modele-de-livre-avec-Jupyter-Book.git/notebooks
- : int = 0

On pourrait envisager de faire pareil pour d’autres « cell magics », en ouvrant un terminal IPython sous-jacent, avec Sys.command, et en lui envoyant le code de la cellule, puis en récupérant la sortie, et afficher la sortie comme une sortie normale, avec Jupyter_notebook.display.

Je ne pense pas avoir le temps de bidouiller tout ça…

4.2.7. D’autres exemples ?

TODO: plus tard !

4.3. Pour en apprendre plus