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 ...
if
pernyataan 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 repeat
fungsi 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 times
denganrepeat
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 i
sebagai input dan kembali i + 1
, ini secara efektif berfungsi sebagai iterator kita yang kita lewati f
setiap waktu.
Kami telah memperbaiki daftar masalah kami juga
- Tidak ada lagi
if
pernyataan 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 repeat
untuk membuatnya menjadi stack-safe.
Kode di bawah ini memang menggunakan variabel yang bisa berubah n
dan x
tetapi perhatikan bahwa semua mutasi dilokalkan ke repeat
fungsi - 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
/ recur
antarmuka untuk perulangan ruang konstan menggunakan ekspresi murni ; tidak ada yang seperti while
itu.
Di sini kita mengabstraksi while
dengan loop
fungsi kita - mencari recur
tipe khusus untuk menjaga loop tetap berjalan. Ketika non- recur
type 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