OK!
Il codice seguente è scritto usando la sintassi ES6 ma potrebbe essere facilmente scritto in ES5 o anche meno. ES6 non è un requisito per creare un "meccanismo per eseguire il ciclo x volte"
Se non è necessario l'iteratore nel callback , questa è l'implementazione più semplice
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'))
Se hai bisogno dell'iteratore , puoi usare una funzione interna denominata con un parametro contatore per iterare per te
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'))
Smetti di leggere qui se non ti piace imparare più cose ...
Ma qualcosa dovrebbe sentirsi fuori di quelli ...
- le
if
dichiarazioni di singoli rami sono brutte - cosa succede sull'altro ramo?
- dichiarazioni / espressioni multiple negli organi della funzione - le preoccupazioni procedurali vengono mescolate?
- implicitamente restituito
undefined
- indicazione della funzione impura, con effetti collaterali
"Non c'è un modo migliore?"
C'è. Rivisitiamo innanzitutto la nostra implementazione iniziale
// times :: Int -> (void -> void) -> void
const times = x => f => {
if (x > 0) {
f() // has to be side-effecting function
times (x - 1) (f)
}
}
Certo, è semplice, ma nota come chiamiamo f()
e non ci facciamo nulla. Questo limita davvero il tipo di funzione che possiamo ripetere più volte. Anche se abbiamo l'iteratore disponibile, f(i)
non è molto più versatile.
E se iniziassimo con un migliore tipo di procedura di ripetizione delle funzioni? Forse qualcosa che sfrutta meglio input e output.
Ripetizione di funzioni generiche
// 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
Sopra, abbiamo definito una repeat
funzione generica che accetta un input aggiuntivo che viene utilizzato per avviare l'applicazione ripetuta di una singola funzione.
// 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)))
Implementazione times
conrepeat
Bene, adesso è facile; quasi tutto il lavoro è già fatto.
// 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'))
Poiché la nostra funzione prende i
come input e ritorna i + 1
, questo funziona efficacemente come iteratore al quale passiamo f
ogni volta.
Abbiamo risolto anche il nostro elenco puntato di problemi
- Niente più brutte
if
dichiarazioni a ramo singolo
- I corpi a espressione singola indicano preoccupazioni ben separate
- Non più inutile, implicitamente restituito
undefined
Operatore virgola JavaScript, il
Nel caso in cui tu abbia problemi a vedere come funziona l'ultimo esempio, dipende dalla tua consapevolezza di uno dei più antichi assi di battaglia di JavaScript; l' operatore virgola : in breve, valuta le espressioni da sinistra a destra e restituisce il valore dell'ultima espressione valutata
(expr1 :: a, expr2 :: b, expr3 :: c) :: c
Nel nostro esempio sopra, sto usando
(i => (f(i), i + 1))
che è solo un modo sintetico di scrivere
(i => { f(i); return i + 1 })
Ottimizzazione delle chiamate di coda
Per quanto sexy siano le implementazioni ricorsive, a questo punto sarebbe irresponsabile per me consigliarle dato che nessuna VM JavaScript posso pensare di supportare la corretta eliminazione delle chiamate di coda - Babele era solita traspilarla, ma è stata "rotta; reimplementerà "status da oltre un anno.
repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded
Pertanto, dovremmo rivisitare la nostra implementazione repeat
per renderla impilabile.
Il codice qui sotto fa utilizzare le variabili mutabili n
e x
ma nota che tutte le mutazioni sono localizzate alla repeat
funzione - nessun cambiamento di stato (mutazioni) sono visibili dall'esterno della funzione
// 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
Molti di voi diranno "ma non è funzionale!" - Lo so, rilassati. Siamo in grado di implementare uno stile loop
/ recur
interfaccia Clojure per il looping dello spazio costante usando espressioni pure ; nessuna di queste while
cose.
Qui riusciamo ad astrarre while
con la nostra loop
funzione: cerca un recur
tipo speciale per mantenere il loop in esecuzione. Quando recur
si incontra un non tipo, il ciclo è terminato e viene restituito il risultato del calcolo
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