TAMAM!
Aşağıdaki kod ES6 sözdizimleri kullanılarak yazılmıştır, ancak ES5 veya daha azıyla da kolayca yazılabilir. ES6, "x kez döngü mekanizması" oluşturmak için bir gereklilik değildir
Geri aramada yineleyiciye ihtiyacınız yoksa , bu en basit uygulamadır
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'))
Yineleyiciye ihtiyacınız varsa , sizin için yinelemek için bir counter parametresi ile adlandırılmış bir iç işlev kullanabilirsiniz
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'))
Daha fazla şey öğrenmek istemiyorsanız, burada okumayı bırakın ...
Ama bunlar hakkında bir şeyler hissetmeli ...
- tek dal
ififadeleri çirkin - diğer dalda ne olur?
- işlev gövdelerinde birden çok ifade / ifade - prosedür kaygıları karışık mı?
- dolaylı olarak iade
undefined- safsızlık, yan etki fonksiyonu göstergesi
"Daha iyi bir yol yok mu?"
Var. Önce ilk uygulamamızı tekrar gözden geçirelim
// times :: Int -> (void -> void) -> void
const times = x => f => {
if (x > 0) {
f() // has to be side-effecting function
times (x - 1) (f)
}
}
Tabii, basit, ama nasıl aradığımızı f()ve onunla hiçbir şey yapmadığımızı fark et. Bu, birçok kez tekrarlayabileceğimiz fonksiyon türünü gerçekten sınırlar. Yineleyicimiz olsa bile, f(i)çok daha fazla yönlü değildir.
Daha iyi bir işlev tekrarlama prosedürü ile başlarsak ne olur? Belki de girdi ve çıktıyı daha iyi kullanan bir şey.
Genel fonksiyon tekrarı
// 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
Yukarıda, repeattek bir işlevin tekrarlanan uygulamasını başlatmak için kullanılan ek bir girdi alan genel bir işlev tanımladık .
// 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)))
Uygulama timesilerepeat
Şimdi bu kolay; işin neredeyse tamamı zaten yapılmış.
// 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'))
İşlevimiz igirdi i + 1olarak alındığından ve geri döndüğünden , bu fher seferinde geçirdiğimiz yineleyici olarak etkili bir şekilde çalışır .
Madde işaretleri listemizi de düzelttik
- Artık çirkin tek dal
ififadesi yok
- Tek ifade gövdeleri hoş bir şekilde ayrılmış endişeleri gösterir
- Artık işe yaramaz, dolaylı olarak iade edilmez
undefined
JavaScript virgül operatörü,
Son örneğin nasıl çalıştığını görmekte sorun yaşıyorsanız, bu JavaScript'in en eski savaş eksenlerinden birine dair farkındalığınıza bağlıdır; virgül operatörü - Kısacası, soldan sağa ifadeleri değerlendirir ve döndürür son değerlendirilen ifadenin değerini
(expr1 :: a, expr2 :: b, expr3 :: c) :: c
Yukarıdaki örneğimizde,
(i => (f(i), i + 1))
ki bu sadece kısa ve öz bir yazma şeklidir
(i => { f(i); return i + 1 })
Kuyruk Çağrısı Optimizasyonu
Özyinelemeli uygulamalar kadar seksi, bu noktada düşünebileceğim hiçbir JavaScript VM'sinin uygun kuyruk çağrısı eliminasyonunu desteklemediği düşünüldüğünde onları tavsiye etmem sorumsuz olurdu - babel onu aktarmak için kullanılır, ancak "kırıldı; yeniden uygulanacak "bir yıldan uzun süredir statü.
repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded
Bu nedenle, repeatyığını güvenli hale getirmek için uygulamamızı tekrar gözden geçirmeliyiz .
Aşağıdaki kod yapar değişken değişkenler kullanmak nve xtüm mutasyonlar lokalize verildiğini ancak not repeathiçbir devlet değişiklikleri (mutasyon) işlevi dışından görülebilir - fonksiyonu
// 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
Bu, birçoğunuzun "ama bu işlevsel değil!" - Biliyorum, sadece rahatla. Saf ifadeler kullanarak sabit boşluk döngüsü için bir Clojure stili loop/ recurarayüzü uygulayabiliriz ; bunların hiçbiri .while
Burada soyut whilebizim ile deplasmanda loopfonksiyonu - bu özel arar recurdöngü çalışır durumda tutmak için tip. Bir türle recurkarşılaşılmadığında döngü tamamlanır ve hesaplamanın sonucu döndürülür
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