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
if
ifadeleri ç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, repeat
tek 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 times
ilerepeat
Ş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 i
girdi i + 1
olarak alındığından ve geri döndüğünden , bu f
her seferinde geçirdiğimiz yineleyici olarak etkili bir şekilde çalışır .
Madde işaretleri listemizi de düzelttik
- Artık çirkin tek dal
if
ifadesi 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, repeat
yığı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 n
ve x
tüm mutasyonlar lokalize verildiğini ancak not repeat
hiç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
/ recur
arayüzü uygulayabiliriz ; bunların hiçbiri .while
Burada soyut while
bizim ile deplasmanda loop
fonksiyonu - bu özel arar recur
döngü çalışır durumda tutmak için tip. Bir türle recur
karşı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