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
ifdichiarazioni 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 repeatfunzione 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 timesconrepeat
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 icome input e ritorna i + 1, questo funziona efficacemente come iteratore al quale passiamo fogni volta.
Abbiamo risolto anche il nostro elenco puntato di problemi
- Niente più brutte
ifdichiarazioni 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 repeatper renderla impilabile.
Il codice qui sotto fa utilizzare le variabili mutabili ne xma nota che tutte le mutazioni sono localizzate alla repeatfunzione - 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/ recurinterfaccia Clojure per il looping dello spazio costante usando espressioni pure ; nessuna di queste whilecose.
Qui riusciamo ad astrarre whilecon la nostra loopfunzione: cerca un recurtipo speciale per mantenere il loop in esecuzione. Quando recursi 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