Programmation Fonctionnelle, TD9

Figure
Version & licenses
Creative Commons License

Programmation Fonctionnelle, TD9 : Boucles fonctionnelles.

Guyslain Naves

Exercice 1 : des boucles fonctionnelles.

On se propose de créer un mécanisme pour faire des boucles façon while ou for tout en restant pûrement fonctionnel : sans assignation.

Pour cela nous proposons la fonction suivante :

  1. let rec repeat init ~f =
  2. match f init with
  3. | `Loop next -> repeat ~f next
  4. | `Stop result -> result
  1. Utilisez repeat pour afficher tous les caractères d'une chaîne jusqu'à la première espace.
  2. Créez une fonction for_loop en utilisant repeat. Elle prend en argument les deux bornes, une fonction et une valeur initiale.
  3. Utilisez for_loop pour écrire la fonction factorielle.

Exercice 2 : monade d'état.

Voici l'interface d'une monade d'état :

  1. type state
  2. type 'value t = state -> 'value * state

  3. val (>>=) : 'value t -> ('value -> 'result t) -> 'result t
  4. val (>>) : 'unit t -> 'result t -> 'result t
  5. val return : 'value -> 'value t

  6. val set : state -> unit t
  7. val get : state t

Il faut comprendre que 'value t est une action produisant une valeur de type 'value. Par ailleurs, cette action a accès à un état qu'elle peut lire ou écrire.

On suppose disposer d'une monade d'état dont le type stateest int.

  1. Définissez l'action print_state : unit t affichant la valeur de l'état.
  2. Définissez l'action incr : unit t augmentant de 1 la valeur de l'état.
  3. Définissez une suite d'actions, consistant à incrémenter deux fois le compteur, l'afficher, l'incrémenter une fois et l'afficher de nouveau.
  4. Soit une fonction apply : int -> unit t. On souhaite créer l'action consistant à faire successivement apply ipour i valant de lower à upper. Définissez une telle boucle for.
  5. Écrivez l'action permettant de calculer la factorielle d'un entier $n$ à l'aide de la question précédente. Vous pouvez modifier l'état arbitrairement.

    1. val fact : int -> int t
  6. Créez une implémentation des monades d'état.

Exercice 3 : yield.

Divers langages de programmation proposent des générateurs, permettant d'énumérer des valeurs en les générant une par une, en se basant sur un mot-clé tel que yield. Un yield ressemble à un return sauf que l'appelant peut ensuite revenir dans la fonction au point juste après le yield. On utilise ensuite une boucle pour traiter toutes las valeurs ainsi générable. Par exemple, on peut écrire en Python :

  1. # from Wikipedia

  2. def countfrom(n):
  3. while True:
  4. yield n
  5. n += 1

  6. for i in countfrom(10):
  7. if i <= 20:
  8. print(i)
  9. else:
  10. break

Cela permet notamment de définir facilement des itérables (comparativement à Java par exemple, essayez de définir un itéreur de nœud d'un arbre par exemple).

La façon naturelle de faire en OCaml est d'utiliser une liste paresseuse. Voici une autre méthode. On définit le module :

  1. module Yield :
  2. sig
  3. type ('value,'response,'final) t =
  4. [ `Done of 'final
  5. | `Yield of 'value * ('response -> ('value, 'response, 'final) t)
  6. ]

  7. val (>>=) :
  8. ('value,'response,'arg) t ->
  9. ('arg -> ('value,'response,'final) t) ->
  10. ('value,'response,'final) t

  11. val (>>) :
  12. ('value,'response,unit) t ->
  13. ('value,'response,'final) t ->
  14. ('value,'response,'final) t

  15. val return : 'final -> ('value,'response,'final) t

  16. val yield :
  17. 'value ->
  18. ('response -> ('value,'response,'final) t) ->
  19. ('value,'response,'final) t

  20. val iterate :
  21. ('elt,'response,'final) t ->
  22. ('elt -> [ `Stop of 'final | `Continue of 'response]) ->
  23. 'final
  24. end =
  25. struct
  26. type ('value,'response, 'final) t =
  27. [ `Done of 'final
  28. | `Yield of 'value * ('response -> ('value, 'response, 'final) t)
  29. ]

  30. let rec (>>=) yielder fct =
  31. match yielder with
  32. | `Done res -> fct res
  33. | `Yield (value, of_response) ->
  34. `Yield (value, fun response -> of_response response >>= fct)

  35. let (>>) yielder1 yielder2 = yielder1 >>= fun () -> yielder2

  36. let return res = `Done res

  37. let yield elt next = `Yield (elt,next)

  38. let rec do_next next fct = function
  39. | `Stop result -> result
  40. | `Continue response -> iterate (next response) fct

  41. and iterate yielder fct =
  42. match yielder with
  43. | `Done result -> result
  44. | `Yield (elt, next) -> do_next next fct (fct elt)
  45. end

Voici la version OCaml du code Python :

  1. open Yield

  2. let rec count_from n =
  3. yield n @@ fun () ->
  4. count_from (n+1)

  5. let _ =
  6. iterate (count_from 10) @@ fun i ->
  7. if i < 20 then `Continue (Printf.printf "%d\n%!" i)
  8. else `Stop ()
  1. Écrivez un itérateur des éléments d'une liste :

    1. val list_iterator : 'elt list -> ('elt, unit, unit) Yield.yielded
  2. Écrivez un itérateur des nœuds d'un arbre binaire :

    1. val tree_iterator : 'a bin_tree -> ('a, unit, unit) Yield.t
  3. Cette version permet à l'utilisateur de l'itérateur de retourner des valeurs au générateur : la communication se passe dans les deux sens. On peut en profiter, par exemple pour faire un algorithme de recherche d'une clé dans un arbre binaire de recherche. Créez les deux fonctions suivantes :

    1. val descent_tree :
    2. 'elt bin_tree -> ('elt, [< `Left | `Right | `This ], 'elt bin_tree) Yield.t
    3. val find_key : 'elt -> 'elt bin_tree -> 'elt bin_tree = <fun>

    find_key retourne le sous-arbre ayant l'élément cherché pour racine, ou une feuille.