BAIK!
Kode di bawah ini ditulis menggunakan sintaks ES6 tetapi bisa dengan mudah ditulis dalam ES5 atau bahkan kurang. ES6 bukan persyaratan untuk membuat "mekanisme untuk mengulang x kali"
Jika Anda tidak memerlukan iterator di callback , ini adalah implementasi paling sederhana
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'))
Jika Anda benar-benar membutuhkan iterator , Anda dapat menggunakan fungsi dalam bernama dengan parameter penghitung untuk beralih untuk Anda
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'))
Berhenti membaca di sini jika Anda tidak suka mempelajari lebih banyak hal ...
Tetapi sesuatu harus merasa tidak enak tentang itu ...
ifpernyataan cabang tunggal jelek - apa yang terjadi di cabang lain?
- banyak pernyataan / ekspresi dalam badan-badan fungsi - apakah masalah prosedur dicampur?
- dikembalikan secara implisit
undefined- indikasi fungsi tidak murni dan efek samping
"Apakah tidak ada cara yang lebih baik?"
Ada. Pertama mari kita meninjau kembali implementasi awal kita
// times :: Int -> (void -> void) -> void
const times = x => f => {
if (x > 0) {
f() // has to be side-effecting function
times (x - 1) (f)
}
}
Tentu, ini sederhana, tetapi perhatikan bagaimana kita menelepon f()dan tidak melakukan apa-apa dengannya. Ini benar-benar membatasi jenis fungsi yang dapat kami ulangi berkali-kali. Bahkan jika kita memiliki iterator yang tersedia, f(i)tidak jauh lebih fleksibel.
Bagaimana jika kita mulai dengan prosedur pengulangan fungsi yang lebih baik? Mungkin sesuatu yang lebih baik menggunakan input dan output.
Pengulangan fungsi generik
// 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
Di atas, kami mendefinisikan repeatfungsi generik yang mengambil input tambahan yang digunakan untuk memulai aplikasi berulang fungsi tunggal.
// 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)))
Melaksanakan timesdenganrepeat
Nah ini mudah sekarang; hampir semua pekerjaan sudah dilakukan.
// 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'))
Karena fungsi kita mengambil isebagai input dan kembali i + 1, ini secara efektif berfungsi sebagai iterator kita yang kita lewati fsetiap waktu.
Kami telah memperbaiki daftar masalah kami juga
- Tidak ada lagi
ifpernyataan cabang tunggal yang jelek
- Badan ekspresi tunggal menunjukkan kekhawatiran yang dipisahkan dengan baik
- Tidak ada lagi yang tidak berguna, dikembalikan secara implisit
undefined
Operator koma JavaScript,
Jika Anda kesulitan melihat bagaimana contoh terakhir bekerja, itu tergantung pada kesadaran Anda tentang salah satu sumbu pertempuran JavaScript tertua; yang operator koma - singkatnya, itu mengevaluasi ekspresi dari kiri ke kanan dan mengembalikan nilai dari ekspresi dievaluasi terakhir
(expr1 :: a, expr2 :: b, expr3 :: c) :: c
Dalam contoh di atas, saya menggunakan
(i => (f(i), i + 1))
yang hanya cara penulisan yang ringkas
(i => { f(i); return i + 1 })
Optimasi Panggilan Ekor
Seberapa seksi implementasi rekursif ini, pada titik ini tidak bertanggung jawab bagi saya untuk merekomendasikan mereka mengingat bahwa tidak ada JavaScript VM yang dapat saya pikirkan mendukung eliminasi panggilan ekor yang tepat - babel digunakan untuk mentransmisikannya, tetapi sudah dalam "rusak; akan reimplement "status lebih dari setahun.
repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded
Karena itu, kita harus meninjau kembali implementasi kita repeatuntuk membuatnya menjadi stack-safe.
Kode di bawah ini memang menggunakan variabel yang bisa berubah ndan xtetapi perhatikan bahwa semua mutasi dilokalkan ke repeatfungsi - tidak ada perubahan status (mutasi) yang terlihat dari luar fungsi
// 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
Ini akan membuat banyak dari Anda mengatakan "tapi itu tidak berfungsi!" - Aku tahu, santai saja. Kita dapat mengimplementasikan gaya-Clojure loop/ recurantarmuka untuk perulangan ruang konstan menggunakan ekspresi murni ; tidak ada yang seperti whileitu.
Di sini kita mengabstraksi whiledengan loopfungsi kita - mencari recurtipe khusus untuk menjaga loop tetap berjalan. Ketika non- recurtype ditemukan, loop selesai dan hasil perhitungan dikembalikan
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