(Sous la direction de this post sur fshub)
La première fois que je suis allé à atteindre pour la pause/continuer à OCaml/F #, il m'a jeté un (infini) boucle, pour ainsi dire, parce qu'une telle chose n'existe pas! Dans OCaml, on peut utiliser des exceptions pour rompre avec une boucle car elles sont très pas cher, mais en F # (en.NET) le temps système est assez élevé et n'est pas utile pour le contrôle de flux "normal". Ceci est apparu lorsque l'on jouait avec des algorithmes de tri il y a un certain temps (pour tuer un peu de temps), ce qui fait un usage intensif de repeat/until et break. Il m'a frappé que les fonctions d'appel de queue récursives peuvent atteindre exactement le même résultat, avec seulement un léger ding à la lisibilité. Donc, j'ai jeté «bDone mutable» et «tandis que pas bDone» et essayé d'écrire le code sans ces constructions impératives. Ce qui suit distille juste les parties en boucle et montre comment écrire le code de style repeat/until, do/while, while/do, break/continue et test-in-the-middle en utilisant des tailcalls. Tout semble fonctionner exactement à la même vitesse qu'une instruction traditionnelle F # 'while', mais votre kilométrage peut varier (certaines plates-formes peuvent ne pas implémenter correctement le tailcall et donc empiler les erreurs jusqu'à ce qu'elles soient corrigées). A la fin est un algorithme de tri (mauvais) écrit dans les deux styles, à titre de comparaison. Commençons par une boucle 'do/while', écrite en style impératif F # traditionnel, puis examinons les variations fonctionnelles qui fournissent à la fois le même type de boucle, ainsi que différentes sémantiques comme while/do, repeat/until, tester au milieu, et même casser/continuer (sans monades .. euh, workflows!).
#light
(* something to work on... *)
let v = ref 0
let f() = incr v;
let g() = !v;
let N = 100000000
let imperDoWhile() =
let mutable x = 0
let mutable bDone = false
while not bDone do
f()
x <- x + 1
if x >= N then bDone <- true
Ok, c'est assez facile. Gardez à l'esprit que f() est toujours appelé au moins une fois (do/while).
Voici le même code, mais dans un style fonctionnel. Notez que nous n'avons pas besoin de déclarer un mutable ici.
let funDoWhile() =
let rec loop x =
f() (*Do*)
if x < N then (*While*)
loop (x+1)
loop 0
Nous pouvons faire tourner cela à un do/while traditionnel en mettant l'appel de fonction dans le bloc if. Comment répéter un bloc jusqu'à ce que certaines conditions soient vraies (répéter/jusqu'à)? Assez facile ...
let funRepeatUntil() =
let rec loop x =
f() (*Repeat*)
if x >= N then() (*Until*)
else loop (x+1)
loop 0
Ce qui était que sur une pause monade moins? Eh bien, introduire une expression conditionnelle qui renvoie « unité », comme dans:
let funBreak() =
let rec loop() =
let x = g()
if x > N then() (*break*)
else
f()
loop()
loop()
Que diriez-vous continuer? Eh bien, c'est juste un autre appel à la boucle! Tout d'abord, avec une béquille de syntaxe:
let funBreakContinue() =
let break'() =()
let rec continue'() =
let x = g()
if x > N then break'()
elif x % 2 = 0 then
f(); f(); f();
continue'()
else
f()
continue'()
continue'()
Et puis encore une fois sans (laid) syntaxe Béquille:
let funBreakContinue'() =
let rec loop() =
let x = g()
if x > N then()
elif x % 2 = 0 then
f(); f(); f();
loop()
else
f()
loop()
loop()
Facile comme bonjour! Un bon résultat de ces formes de boucle est qu'il est plus facile de repérer et d'implémenter des états dans vos boucles. Par exemple, un tri à bulles boucle continuellement sur un tableau entier, en échangeant des valeurs qui ne sont pas à leur place au moment où il les trouve. Il garde la trace de savoir si un passage sur le tableau a produit des échanges. Si non, alors chaque valeur doit être au bon endroit, de sorte que le tri peut se terminer. En tant qu'optimisation, à chaque passage dans le tableau, la dernière valeur du tableau finit par être triée au bon endroit. Ainsi, la boucle peut être raccourcie d'un à chaque fois. La plupart des algorithmes vérifient un swap et mettent à jour un drapeau "bModified" chaque fois qu'il y en a un. Cependant, une fois le premier échange effectué, cette affectation n'est plus nécessaire; c'est déjà réglé à vrai!
Voici le code F # qui implémente un tri à bulles (oui, le tri à bulles est un algorithme redoutable, les roches quicksort). À la fin est une mise en œuvre impérative qui ne change pas d'état; il met à jour le drapeau bModified pour chaque échange.Fait intéressant, la solution impérative est plus rapide sur les réseaux minuscules et seulement un pour cent ou deux plus lent sur les grands. (Fait pour un bon exemple, cependant).
let inline sort2 f i j (a:'a array) =
let i' = a.[ i ]
let j' = a.[ j ]
if f i' j' > 0 then
a.[ i ] <- j'
a.[ j ] <- i'
let bubble f (xs:'a array) =
if xs.Length = 0
then()
let rec modified i endix =
if i = endix then
unmodified 0 (endix-1)
else
let j = i+1
sort2 f i j xs
modified j endix
and unmodified i endix =
if i = endix then
()
else
let j = i+1
let i' = xs.[ i ]
let j' = xs.[ j ]
if f i' j' > 0 then
xs.[ i ] <- j'
xs.[ j ] <- i'
modified j endix
else
unmodified j endix
in unmodified 0 (xs.Length-1)
let bubble_imperitive f (xs:'a array) =
let mutable bModified = true
let mutable endix = xs.Length - 1
while bModified do
bModified <- false
endix <- endix - 1
for i in 0..endix do
let j = i+1
let i' = xs.[ i ]
let j' = xs.[ j ]
if f i' j' > 0 then
xs.[ i ] <- j'
xs.[ j ] <- i'
bModified <- true
done
done
Devrait probablement être wiki communautaire - pas de «réponse» en soi? – Anthony
@ Anthony, j'espère qu'il y aura un site web, mais s'il n'y en a pas, alors je ferai celui-ci. – Unknown
On dirait que vous avez fait cette communauté trop tôt - vérifiez pleac-ocaml – Thelema