Dans cet exercise on doit construire une caisse de primeur qui accepte en entrée (console) des fruits et qui pour chaque entrée affiche le total sur la sortie (console). Pour cela je vais construire une fonction buy qui va s'appuyer sur la fonction read-line qui lit une ligne du console et println qui affiche une ligne sur la console
Premier besoin : je ne veux pas de boucle fini dans ma fonction - quand j'entre une ligne vide la fonction sort. La syntaxe ici vient de Midje - un framework de test qui a la particularité (pour un langage fonctionnel) d'être orienté outside-in.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(facts "it exits when we enter an empty line" | |
(buy) => anything ;; il n'est pas important ce que renvoie la fonction buy | |
(provided | |
(read-line) => "") ;; étant donnée que read-line renvoie chaîne vide | |
;; // fin du premier test | |
(buy) => anything | |
(provided | |
(read-line) =streams=> ["b" ""])) ;; read-line renvoie d'abord "b" puis chaîne vide |
Ici j'aime vraiment la facilité avec laquelle on arrive à faire un bouchon de read-line - on déclare simplement que ceci est un fait (fact) si read-line renvoie les valeurs énumérés
Deuxième besoin : On doit afficher le total de l'achat après chaque ligne d'entrée.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(fact "it prints the total for every line of input" | |
(buy) => anything | |
(provided | |
(read-line) =streams=> ["a" "c" ""] | |
(println "total: " 100) => anything :times 1 ;; after adding the "a" (apple) | |
;; that costs 100, println should | |
;; be called with "total: 100" | |
(println "total: " 175) => anything :times 1)) ;; after adding "c" (cherries) | |
;; that cost 75, println should | |
;; be called with "total: 175" |
Ici c'est facile, court et ça reste lisible mais il y a du bruit :
- on est obligé de déclarer la valeur de retour attendue de println (qui renvoie toujours nil), j'ai mis anything pour signifier que ce n'est pas important
- chaque contrainte sur println (d'abord total 100 puis total 175) se déclare sur une ligne. Nous n'avons pas l'expressitivité de du bouchon avec =streams=>. Dans mes rêves ça aurait été
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(fact "it prints the total for every line of input" | |
(buy) => anything ;; toujous sans importance | |
(provided | |
(read-line) =streams=> ["a" "c" ""]) | |
(side-effects | |
(println) =receives=> [["total: " 100] | |
["total: " 175]])) |
Et sinon le code ça rend quoi?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; just enter an empty line and you're ready for the next customer | |
(defn buy [] | |
(loop [items []] | |
(let [item (read-line) ;; reads from console | |
basket (concat (csv-to-col item) items) ;; add item (can be several items separated by comma) to existing items | |
total (apply basket-price basket)] ;; total basket price | |
(if-not (empty? item) | |
(do ;; only one instruction can follow if-not, | |
(println "total: " total) ;; and since we need to both print the total | |
(recur basket)))))) ;; and recur back to the loop point we wrap | |
;; in a do statement |
Ce à quoi je m'attendais pas en commençant cette exercise c'est que bien qu'on gère de la mutabilité (le prix total du panier s'incrémente) aucune fonction n'est mutable et aucun variable mutable n'est nécessaire. L'état du panier est contenu dans la recursion.
Pour moi c'est fascinant de découvrir ce moyen de gérer l'état visible d'une application sans mutabilité.