D'ACCORD!
Le code ci-dessous est écrit en utilisant les syntaxes ES6 mais pourrait tout aussi bien être écrit en ES5 ou même moins. ES6 n'est pas une exigence pour créer un "mécanisme pour boucler x fois"
Si vous n'avez pas besoin de l'itérateur dans le rappel , c'est l'implémentation la plus simple
const times = x => f => {
if (x > 0) {
f()
times (x - 1) (f)
}
}
// use it
times (3) (() => console.log('hi'))
// or define intermediate functions for reuse
let twice = times (2)
// twice the power !
twice (() => console.log('double vision'))
Si vous avez besoin de l'itérateur , vous pouvez utiliser une fonction interne nommée avec un paramètre de compteur pour itérer pour vous
const times = n => f => {
let iter = i => {
if (i === n) return
f (i)
iter (i + 1)
}
return iter (0)
}
times (3) (i => console.log(i, 'hi'))
Arrêtez de lire ici si vous n'aimez pas apprendre plus de choses ...
Mais quelque chose devrait se sentir mal à propos de ceux-ci ...
- les
if
déclarations de branche unique sont laides - que se passe-t-il sur l'autre branche?
- déclarations / expressions multiples dans les corps de fonction - les problèmes de procédure sont-ils mélangés?
- renvoyé implicitement
undefined
- indication d'une fonction impure à effet secondaire
"N'y a-t-il pas un meilleur moyen?"
Il y a. Revoyons d'abord notre implémentation initiale
// times :: Int -> (void -> void) -> void
const times = x => f => {
if (x > 0) {
f() // has to be side-effecting function
times (x - 1) (f)
}
}
Bien sûr, c'est simple, mais remarquez comment nous appelons f()
et ne faisons rien avec. Cela limite vraiment le type de fonction que nous pouvons répéter plusieurs fois. Même si nous avons l'itérateur disponible, ce f(i)
n'est pas beaucoup plus polyvalent.
Et si nous commençons avec un meilleur type de procédure de répétition de fonction? Peut-être quelque chose qui fait un meilleur usage des entrées et des sorties.
Répétition de fonction générique
// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
if (n > 0)
return repeat (n - 1) (f) (f (x))
else
return x
}
// power :: Int -> Int -> Int
const power = base => exp => {
// repeat <exp> times, <base> * <x>, starting with 1
return repeat (exp) (x => base * x) (1)
}
console.log(power (2) (8))
// => 256
Ci-dessus, nous avons défini une repeat
fonction générique qui prend une entrée supplémentaire qui est utilisée pour démarrer l'application répétée d'une seule fonction.
// repeat 3 times, the function f, starting with x ...
var result = repeat (3) (f) (x)
// is the same as ...
var result = f(f(f(x)))
Mettre times
en œuvre avecrepeat
Eh bien, c'est facile maintenant; presque tout le travail est déjà fait.
// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
if (n > 0)
return repeat (n - 1) (f) (f (x))
else
return x
}
// times :: Int -> (Int -> Int) -> Int
const times = n=> f=>
repeat (n) (i => (f(i), i + 1)) (0)
// use it
times (3) (i => console.log(i, 'hi'))
Puisque notre fonction prend i
comme entrée et retourne i + 1
, cela fonctionne effectivement comme notre itérateur auquel nous passonsf
chaque fois.
Nous avons également corrigé notre liste de problèmes
- Plus de branche unique laide
if
déclarations de
- Les corps à expression unique indiquent des préoccupations bien séparées
- Plus inutile, retourné implicitement
undefined
Opérateur de virgule JavaScript, le
Au cas où vous auriez du mal à voir comment le dernier exemple fonctionne, cela dépend de votre connaissance de l'un des plus anciens axes de combat de JavaScript; l' opérateur virgule - en bref, il évalue les expressions de gauche à droite et renvoie la valeur de la dernière expression évaluée
(expr1 :: a, expr2 :: b, expr3 :: c) :: c
Dans notre exemple ci-dessus, j'utilise
(i => (f(i), i + 1))
qui n'est qu'une manière succincte d'écrire
(i => { f(i); return i + 1 })
Optimisation des appels de queue
Aussi sexy que soient les implémentations récursives, à ce stade, il serait irresponsable de ma part de les recommander étant donné qu'aucune VM JavaScript à laquelle je pense ne prend en charge l'élimination correcte des appels de queue - babel avait l'habitude de la transpiler, mais elle a été "cassée; sera réimplémentée "statut depuis plus d'un an.
repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded
À ce titre, nous devrions revoir notre mise en œuvre de repeat
pour la rendre sûre.
Le code ci - dessous utilise des variables mutables n
et x
notez que toutes les mutations sont localisées dans la repeat
fonction - aucun changement d'état (mutation) n'est visible de l'extérieur de la fonction
// repeat :: Int -> (a -> a) -> (a -> a)
const repeat = n => f => x =>
{
let m = 0, acc = x
while (m < n)
(m = m + 1, acc = f (acc))
return acc
}
// inc :: Int -> Int
const inc = x =>
x + 1
console.log (repeat (1e8) (inc) (0))
// 100000000
Vous serez nombreux à dire "mais ce n'est pas fonctionnel!" - Je sais, détends-toi. Nous pouvons implémenter une interface loop
/ style Clojure recur
pour la boucle d'espace constant en utilisant des expressions pures ; rien de tout while
ça.
Ici, nous résumons while
notre loop
fonction - elle recherche un recur
type spécial pour maintenir la boucle en cours d'exécution. Lorsqu'un non- recur
type est rencontré, la boucle est terminée et le résultat du calcul est retourné
const recur = (...args) =>
({ type: recur, args })
const loop = f =>
{
let acc = f ()
while (acc.type === recur)
acc = f (...acc.args)
return acc
}
const repeat = $n => f => x =>
loop ((n = $n, acc = x) =>
n === 0
? acc
: recur (n - 1, f (acc)))
const inc = x =>
x + 1
const fibonacci = $n =>
loop ((n = $n, a = 0, b = 1) =>
n === 0
? a
: recur (n - 1, b, a + b))
console.log (repeat (1e7) (inc) (0)) // 10000000
console.log (fibonacci (100)) // 354224848179262000000