Premiers pas en OCaml/Filtrage de motif
Le filtrage de motif permet soit de gérer différents cas en fonction des valeurs d'une expression, soit d'accéder aux éléments d'un type construit (ou les deux à la fois).
Principe général
modifierLe filtrage de motif s'effectue avec les mots clefs match
et with
encadrant l’expression que l’on souhaite manipuler, suivi d'un ou plusieurs motifs, et à chaque motif est associée une expression à retourner.
Filtrage de motif sur les entiers
modifierEn fonction de la valeur de l’expression Random.int 6
différentes chaînes de caractère peuvent être retournées :
# match Random.int 6 with
| 0 -> "chou-fleur"
| 1 -> "cerise"
| 2 -> "chocolat"
| _ -> "poivre"
;;
- : string = "chou-fleur"
Le caractère souligné à la fin est un joker qui récupère toutes les valeurs possibles qui n'ont pas été listées avant lui.
Filtrage de motif sur les listes
modifierDéconstruction
modifierLe filtrage de motif est le moyen privilégié pour manipuler les listes.
On peut alors accéder à un ou plusieurs éléments du côté de la tête, séparé(s) de la queue de la liste, et gérer le cas où la liste est vide.
# match [1; 2; 3; 4; 5] with
| head :: tail -> tail
| [] -> []
;;
- : int list = [2; 3; 4; 5]
Ici on renvoie la queue de la liste au cas où la liste en entrée n’est pas vide, et on renvoie une liste vide si la liste en entrée est vide elle aussi.
Il est possible de déconstruire plusieurs éléments de la tête de la liste :
# match [1; 2; 3; 4; 5; 6] with
| h1 :: h2 :: h3 :: tail -> h1 + h2 + h3
| h1 :: h2 :: tail -> h1 + h2
| h1 :: tail -> h1
| [] -> 0
;;
- : int = 6
# match [1; 2] with
| h1 :: h2 :: h3 :: tail -> h1 + h2 + h3
| h1 :: h2 :: tail -> h1 + h2
| h1 :: tail -> h1
| [] -> 0
;;
- : int = 3
Définissons ce filtrage de motif dans une fonction pour tester plusieurs valeurs d'entrée :
# let sum_head_until_3 lst =
match lst with
| h1 :: h2 :: h3 :: tail -> h1 + h2 + h3
| h1 :: h2 :: tail -> h1 + h2
| h1 :: tail -> h1
| [] -> 0
;;
val sum_head_until_3 : int list -> int = <fun>
# sum_head_until_3 [1; 2; 3; 4; 5] ;;
- : int = 6
# sum_head_until_3 [5; 6] ;;
- : int = 11
# sum_head_until_3 [15] ;;
- : int = 15
# sum_head_until_3 [] ;;
- : int = 0
L'ordre dans lequel les motifs sont placés est parfois significatif, c’est le cas ici.
OCaml parcourt les motifs potentiels du premier au dernier dans l'ordre. C'est donc le premier motif qui peut correspondre à la valeur de l’expression d'entrée qui sera sélectionné, et c’est l’expression correspondant à ce motif qui sera retournée.
Voyons ce qui se passe avec les mêmes motifs spécifiés dans l’ordre inverse :
# match [1; 2; 3; 4; 5; 6] with
| [] -> 0
| h1 :: tail -> h1
| h1 :: h2 :: tail -> h1 + h2
| h1 :: h2 :: h3 :: tail -> h1 + h2 + h3
;;
Warning 11: this match case is unused.
Warning 11: this match case is unused.
- : int = 1
Ici le deuxième motif récupérera tous les éléments qui auraient pu correspondre pour le deuxième ou le troisième motif. En conséquence le deuxième et le troisième motif ne seront jamais utilisés. C'est pourquoi OCaml nous indique deux avertissements à ce sujet.
Filtrage des valeurs possibles
modifierLe filtrage de motif peut aussi servir à traiter différentes valeurs possibles de l’expression d'entrée.
# let head_is lst =
match lst with
| 1 :: tail -> "head is one"
| 2 :: tail -> "head is two"
| 3 :: tail -> "head is three"
| head :: tail -> "head = " ^ (string_of_int head)
| [] -> "list is empty"
;;
val head_is : int list -> string = <fun>
# head_is [1; 10; 20; 30] ;;
- : string = "head is one"
# head_is [2; 40; 50; 60] ;;
- : string = "head is two"
# head_is [70; 80; 90] ;;
- : string = "head = 70"
# head_is [] ;;
- : string = "list is empty"
Filtrage de motif sur les tableaux
modifierLe filtrage de motif est rarement utilisé sur les tableaux. D'une part cela requiert de connaître sa taille ou ses tailles possibles, et d’autre part cela n’est pas très idiomatique.
Voici cependant un exemple où le filtrage de motif s'applique sur le tableau Sys.argv
ce qui permet de traiter différentes valeurs possibles de la ligne de commande avec lesquelles le script est appelé :
Copiez dans un fichier printsys.ml
:
let () =
match Sys.argv with
| [| _; "--os-type" |] -> print_endline Sys.os_type
| [| _; "--word-size" |] -> print_int Sys.word_size; print_newline ()
| _ ->
prerr_endline ("Usage:\n" ^ Sys.argv.(0) ^
" ( --os-type | --word-size )")
Appelez ce script avec l'interpréteur ocaml
:
$ ocaml printsys.ml Usage: printsys.ml ( --os-type | --word-size ) $ ocaml printsys.ml --os-type Unix $ ocaml printsys.ml --word-size 32 $ ocaml printsys.ml --banane Usage: printsys.ml ( --os-type | --word-size )
Filtrage de motif sur les tuples
modifierVoici un exemple :
# match (1, 2, 3) with
| (1, b, c) -> b + c
| (a, 2, c) -> a + c
| (a, b, 3) -> a + b
| _ -> 0
;;
- : int = 5
Le filtrage peut contenir des valeurs données qui feront sélectionner une entrée donnée, et des variables qui récupéreront les autres valeurs utilisées dans le code qui suit cette entrée. Ici la première entrée est sélectionnée car les entrées sont testées dans l'ordre.
Filtrage de motif sur les variants
modifierReprenons le type my_variant défini à la page précédente :
type my_variant = Alpha | Beta | Gamma | Delta
Le filtrage de motif de ce variant se fera comme suit :
# let v_to_string v =
match v with
| Alpha -> "Alpha"
| Beta -> "Beta"
| Gamma -> "Gamma"
| Delta -> "Delta"
;;
val v_to_string : my_variant -> string = <fun>
# v_to_string Beta ;;
- : string = "Beta"
Filtrage de motif sur les enregistrements
modifierReprenons le type vector défini à la page précédente, définissons une variable de ce type et réalisons un filtrage avec :
# type vector = {
x: float;
y: float;
z: float;
} ;;
type vector = { x : float; y : float; z : float; }
# let v = { x = 1.0; y = 2.0; z = 3.0 } ;;
val v : vector = {x = 1.; y = 2.; z = 3.}
# match v with { x; y; z } -> x +. y +. z ;;
- : float = 6.
Il est possible de ne filtrer qu'un des éléments de l'enregistrement :
# match v with { x } -> x ;;
- : float = 1.
Il est également possible de filtrer pour une valeur particulière d'un des champs de l'enregistrement :
# match v with { x = 1.0; y; z } -> (y, z) | _ -> (0.0, 0.0) ;;
- : float * float = (2., 3.)