Certaines boucles ne peuvent pas s'écrire comme « fais ceci pour chaque élément d'une séquence » : il faut continuer jusqu'à ce qu'une condition bascule. Le combinateur while de Factor est l'outil qu'il te faut pour ça : une quotation prédicat qui demande « on continue ? », une quotation corps qui effectue le travail d'une étape, et while répète jusqu'à ce que le prédicat renvoie f.
while et until
while ( pred body -- )
until ( pred body -- )
pred et body sont tous deux des quotations qui opèrent sur la pile de données. while continue de boucler tant que pred laisse une valeur vraie ; until fait l'inverse : il continue de boucler jusqu'à ce que pred devienne vrai (autrement dit, tant que pred laisse f).
Une même valeur d'état peut circuler sur la pile entre le prédicat et le corps :
USING: kernel math ;
5 [ dup 0 > ] [ dup . 1 - ] while drop
! prints 5 4 3 2 1
Déroulons le fil : le prédicat dup 0 > jette un œil au compteur en cours ; le corps dup . 1 - l'affiche puis le décrémente ; la boucle s'arrête dès que le compteur atteint 0. Le drop final retire ce dernier 0 de la pile.
while* — garder la valeur du prédicatwhile* ( pred body -- )
while se contente de regarder si son prédicat a laissé une valeur vraie : il jette la valeur elle-même avant d'exécuter le corps. while*, lui, transmet cette valeur au corps. Utilise-le dès que le test et le corps veulent la même valeur fraîchement calculée : une recherche, une correspondance, un jeton analysé.
Ici, chaque nom pointe vers le suivant dans une chaîne. Le prédicat recherche le nom courant ; tant qu'il existe un nom suivant, while* le transmet au corps, qui l'affiche et repart de là :
USING: assocs kernel locals prettyprint ;
:: print-chain ( start links -- )
start [ links at ] [ dup . ] while* ;
"a" H{ { "a" "b" } { "b" "c" } } print-chain
! prints "b" then "c"
links at renvoie le nom suivant, ou f dès qu'un nom n'a pas d'entrée. Tant qu'il renvoie un nom, while* le donne à dup ., qui l'affiche et le laisse comme clé pour la recherche suivante. Le premier f arrête la boucle : while* le jette, donc il ne reste rien.
Quand la boucle transporte plus d'une valeur, par exemple une liste restante et un total cumulé, le jonglage avec la pile devient bruyant. Le motif le plus propre consiste à déclarer des variables locales avec :: :
:: word ( inputs -- outputs ) ... ;
À l'intérieur du corps, :> name! ajoute une variable locale mutable ; les lectures utilisent name, les écritures utilisent name! (qui consomme la nouvelle valeur sur la pile et l'affecte).
USING: kernel locals math sequences ;
! Walk a sequence from the front, summing as we go
:: sum-by-hand ( seq -- total )
seq :> rest!
0 :> running!
[ rest empty? ] [
rest unclip [ rest! ] dip
running + running!
] until
running ;
Deux variables locales ici (rest et running) couvrent tout l'état de la boucle : le prédicat les inspecte, le corps les met à jour, et la valeur finale est renvoyée à la fin.
unclip ( seq -- rest first ) détache le premier élément d'une séquence, ce qui est pratique quand la boucle veut consommer une liste élément par élément. La forme [ rest! ] dip met à jour rest avec la queue restante tout en laissant l'élément fraîchement dépilé sur la pile à la disposition du corps.
Ton amie Li Mei tient un bar à jus où elle vend de délicieux jus de fruits mélangés. Tu es un client régulier de sa boutique et tu as réalisé que tu pourrais lui faciliter un peu la vie avec du code Factor.
Li Mei aime dire à ses clients à l'avance combien de temps ils devront attendre pour un jus de la carte. Elle a du mal à se rappeler les chiffres exacts, car le temps nécessaire pour mixer les jus varie :
"Pure Strawberry Joy" prend 0.5 minute."Energizer" et "Green Garden" prennent 1.5 minute chacun."Tropical Island" prend 3 minutes."All or Nothing" prend 5 minutes.2.5 minutes.Définis time-to-mix-juice pour qu'elle retire de la pile le nom d'un jus et renvoie le nombre de minutes qu'il faut pour mixer cette boisson.
"Tropical Island" time-to-mix-juice .
! => 3
"Berries & Lime" time-to-mix-juice .
! => 2.5
Beaucoup des créations de Li Mei contiennent des quartiers de citron vert, que ce soit comme ingrédient ou pour la décoration. Aussi, quand elle commence son service le matin, elle doit s'assurer que la réserve de quartiers de citron vert est bien pleine pour la journée à venir.
Définis wedges-from-lime pour qu'elle prenne une taille de citron vert et renvoie le nombre de quartiers qu'il produit : 6 pour un citron vert "small", 8 pour un "medium", 10 pour un "large".
Définis ensuite limes-to-cut pour qu'elle prenne le nombre de quartiers needed et un tableau de limes (des tailles de citron vert, dans l'ordre de découpe). Elle découpe les citrons verts un par un, dans l'ordre, et s'arrête dès qu'elle a assez de quartiers ou qu'elle n'a plus de citrons verts. Renvoie le nombre de citrons verts qu'il a fallu découper.
25 { "small" "small" "large" "medium" "small" } limes-to-cut .
! => 4
Li Mei aime garder une trace du temps qu'il faudra pour mixer les commandes des clients qui attendent.
Définis order-times pour qu'elle prenne un tableau de commandes et renvoie un tableau contenant le temps de mixage de chaque commande, dans le même ordre.
{ "Energizer" "Tropical Island" } order-times .
! => { 1.5 3 }
Li Mei travaille toujours jusqu'à 15 h. Ensuite, son employé Dmitry prend le relais. Il arrive souvent que des boissons aient été commandées mais pas encore préparées quand le service de Li Mei se termine ; c'est alors Dmitry qui prépare les jus restants.
Pour faciliter la passation, définis remaining-orders pour qu'elle prenne le nombre de minutes time-left du service de Li Mei et un tableau d'orders (des jus qui ont été commandés mais pas encore préparés). Renvoie les commandes que Dmitry devra prendre en charge, c'est-à-dire celles que Li Mei n'a pas le temps de commencer avant la fin de son service.
time-left est toujours supérieur à 0. Les commandes sont préparées dans l'ordre dans lequel elles apparaissent dans le tableau. Une fois que Li Mei a commencé un jus, elle le termine toujours, même si elle doit travailler un peu plus longtemps. Si elle a le temps de commencer toutes les commandes, renvoie un tableau vide.
5 { "Energizer" "All or Nothing" "Green Garden" } remaining-orders .
! => { "Green Garden" }
Inscris-toi sur Exercism pour apprendre et maîtriser Factor avec 47 concepts163 exercices, et un vrai mentorat humain, le tout gratuitement.