U birleştirici
Bir işlevi argüman olarak kendisine ileterek, bir işlev adı yerine parametresini kullanarak tekrar edebilir! Yani verilen fonksiyonU
, işleve (kendisine) bağlanacak en az bir parametresi olmalıdır.
Aşağıdaki örnekte çıkış koşulumuz yok, bu nedenle bir yığın taşması gerçekleşene kadar süresiz döngü yapacağız.
const U = f => f (f) // call function f with itself as an argument
U (f => (console.log ('stack overflow imminent!'), U (f)))
Sonsuz özyinelemeyi çeşitli teknikler kullanarak durdurabiliriz. Burada, bir girdi bekleyen başka bir anonim işlevi döndürmek için anonim işlevimizi yazacağım ; bu durumda, bir numara. Bir sayı verildiğinde, 0'dan büyükse, yinelemeye devam ederiz, aksi takdirde 0 döndürür.
const log = x => (console.log (x), x)
const U = f => f (f)
// when our function is applied to itself, we get the inner function back
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)
// returns: (x => x > 0 ? U (f) (log (x - 1)) : 0)
// where f is a reference to our outer function
// watch when we apply an argument to this function, eg 5
U (f => x => x > 0 ? U (f) (log (x - 1)) : 0) (5)
// 4 3 2 1 0
Burada hemen belli olmayan şey, fonksiyonumuz, U
birleştirici kullanılarak kendisine ilk uygulandığında , ilk girdiyi bekleyen bir fonksiyon döndürmesidir. Buna bir isim verirsek, lambdalar (anonim fonksiyonlar) kullanarak özyinelemeli fonksiyonları etkin bir şekilde inşa edebiliriz.
const log = x => (console.log (x), x)
const U = f => f (f)
const countDown = U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)
countDown (5)
// 4 3 2 1 0
countDown (3)
// 2 1 0
Yalnızca bu doğrudan özyineleme değildir - kendi adını kullanarak kendisini çağıran bir işlev. Bizim tanımımız countDown
, vücudunun içinde kendisine atıfta bulunmuyor ve yine de yineleme mümkündür
// direct recursion references itself by name
const loop = (params) => {
if (condition)
return someValue
else
// loop references itself to recur...
return loop (adjustedParams)
}
// U combinator does not need a named reference
// no reference to `countDown` inside countDown's definition
const countDown = U (f => x => x > 0 ? U (f) (log (x - 1)) : 0)
U birleştirici kullanılarak mevcut bir işlevden öz referans nasıl kaldırılır
Burada, kendisine bir referans kullanan özyinelemeli bir işlevi nasıl alacağınızı ve bunu öz referans yerine U birleştiriciyi kullanan bir işleve nasıl değiştireceğinizi göstereceğim.
const factorial = x =>
x === 0 ? 1 : x * factorial (x - 1)
console.log (factorial (5)) // 120
Şimdi U birleştiriciyi kullanarak iç referansı değiştirmek için factorial
const U = f => f (f)
const factorial = U (f => x =>
x === 0 ? 1 : x * U (f) (x - 1))
console.log (factorial (5)) // 120
Temel değiştirme modeli budur. Zihinsel bir not alın, sonraki bölümde benzer bir strateji kullanacağız.
// self reference recursion
const foo = x => ... foo (nextX) ...
// remove self reference with U combinator
const foo = U (f => x => ... U (f) (nextX) ...)
Y birleştirici
ilgili: U ve Y birleştiricileri bir ayna benzetmesi kullanarak açıkladı
Önceki bölümde, kendi kendine referans özyinelemesinin, U birleştirici kullanılarak adlandırılmış bir işleve dayanmayan özyinelemeli bir işleve nasıl dönüştürüleceğini gördük. İşlevi her zaman ilk argüman olarak kendisine aktarmayı hatırlamak zorunda kalmanın biraz sıkıntısı var. Y-birleştirici, U-birleştiriciye dayanıyor ve bu sıkıcı kısmı ortadan kaldırıyor. Bu iyi bir şey çünkü karmaşıklığı kaldırmak / azaltmak, işlevleri yapmamızın birincil nedenidir
İlk olarak, kendi Y-birleştiricimizi türetelim
// standard definition
const Y = f => f (Y (f))
// prevent immediate infinite recursion in applicative order language (JS)
const Y = f => f (x => Y (f) (x))
// remove reference to self using U combinator
const Y = U (h => f => f (x => U (h) (f) (x)))
Şimdi kullanımının U-birleştiriciye kıyasla nasıl olduğunu göreceğiz. Dikkat edin, tekrarlamak yerine U (f)
basitçe arayabilirizf ()
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
Y (f => (console.log ('stack overflow imminent!'), f ()))
Şimdi countDown
kullanarak programı göstereceğim Y
- programların neredeyse aynı olduğunu göreceksiniz ancak Y birleştirici işleri biraz daha temiz tutuyor
const log = x => (console.log (x), x)
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const countDown = Y (f => x => x > 0 ? f (log (x - 1)) : 0)
countDown (5)
// 4 3 2 1 0
countDown (3)
// 2 1 0
Ve şimdi göreceğiz factorial
yanı
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const factorial = Y (f => x =>
x === 0 ? 1 : x * f (x - 1))
console.log (factorial (5)) // 120
Gördüğünüz gibi, f
özyineleme mekanizmasının kendisi haline geliyor. Tekrarlamak için buna sıradan bir işlev diyoruz. Bunu farklı argümanlarla birçok kez arayabiliriz ve sonuç yine de doğru olacaktır. Sıradan bir fonksiyon parametresi olduğu için, onu recur
aşağıdaki gibi istediğimiz gibi adlandırabiliriz -
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const fibonacci = Y (recur => n =>
n < 2 ? n : recur (n - 1) + (n - 2))
console.log (fibonacci (10)) // 55
1'den fazla parametreye sahip U ve Y birleştirici
Yukarıdaki örneklerde, hesaplamamızın "durumunu" takip etmek için nasıl bir argüman oluşturup geçebileceğimizi gördük. Peki ya ek durumu takip etmemiz gerekirse?
Biz olabilir bir Array falan gibi bileşik verileri kullanmak ...
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const fibonacci = Y (f => ([a, b, x]) =>
x === 0 ? a : f ([b, a + b, x - 1]))
// starting with 0 and 1, generate the 7th number in the sequence
console.log (fibonacci ([0, 1, 7]))
// 0 1 1 2 3 5 8 13
Ancak bu kötü çünkü iç durumu açığa çıkarıyor (sayaçlar a
ve b
). Sadece arayabilseydik güzel olurdufibonacci (7)
cevabı almak için .
Curried fonksiyonlar hakkında bildiklerimizi (tekli (1-paramter) fonksiyon dizileri) kullanarak, tanımımızı değiştirmek zorunda kalmadan hedefimize kolayca ulaşabiliriz. Y
, birleşik verilere veya gelişmiş dil özelliklerine veya güvenmek .
fibonacci
Aşağıda yakından tanımına bakın. Hemen uyguluyoruz 0
ve 1
bunlara bağlı olan a
ve b
sırasıyla. Şimdi fibonacci, bağlanacak olan son argümanın sağlanmasını bekliyor x
. Yinelediğimizde, çağırmalıyız f (a) (b) (x)
(değil f (a,b,x)
) çünkü fonksiyonumuz curried formdadır.
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const fibonacci = Y (f => a => b => x =>
x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1)
console.log (fibonacci (7))
// 0 1 1 2 3 5 8 13
Bu tür bir kalıp, her türden işlevi tanımlamak için yararlı olabilir. Kullandığımız tanımlanan iki işlevleri göreceksiniz Aşağıda Y
bir bağdaştırıcının ( range
ve reduce
) ve bir türevini reduce
, map
.
const U = f => f (f)
const Y = U (h => f => f (x => U (h) (f) (x)))
const range = Y (f => acc => min => max =>
min > max ? acc : f ([...acc, min]) (min + 1) (max)) ([])
const reduce = Y (f => g => y => ([x,...xs]) =>
x === undefined ? y : f (g) (g (y) (x)) (xs))
const map = f =>
reduce (ys => x => [...ys, f (x)]) ([])
const add = x => y => x + y
const sq = x => x * x
console.log (range (-2) (2))
// [ -2, -1, 0, 1, 2 ]
console.log (reduce (add) (0) ([1,2,3,4]))
// 10
console.log (map (sq) ([1,2,3,4]))
// [ 1, 4, 9, 16 ]
TAMAMEN ANONİM OMG
Burada saf fonksiyonlarla çalıştığımız için, adlandırılmış herhangi bir fonksiyonu tanımının yerine koyabiliriz. Fibonacci'yi aldığımızda ve adlandırılmış fonksiyonları ifadeleriyle değiştirdiğimizde ne olduğunu izleyin
/* const U = f => f (f)
*
* const Y = U (h => f => f (x => U (h) (f) (x)))
*
* const fibonacci = Y (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1)
*
*/
/*
* given fibonacci (7)
*
* replace fibonacci with its definition
* Y (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
*
* replace Y with its definition
* U (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
//
* replace U with its definition
* (f => f (f)) U (h => f => f (x => U (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
*/
let result =
(f => f (f)) (h => f => f (x => h (h) (f) (x))) (f => a => b => x => x === 0 ? a : f (b) (a + b) (x - 1)) (0) (1) (7)
console.log (result) // 13
Ve işte var - fibonacci (7)
anonim işlevlerden başka hiçbir şey kullanmadan özyinelemeli olarak hesaplanır