ĐỒNG Ý!
Mã dưới đây được viết bằng cú pháp ES6 nhưng có thể dễ dàng được viết bằng ES5 hoặc thậm chí ít hơn. ES6 không phải là một yêu cầu để tạo ra một "cơ chế lặp x lần"
Nếu bạn không cần iterator trong cuộc gọi lại , đây là cách thực hiện đơn giản nhất
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'))
Nếu bạn cần iterator , bạn có thể sử dụng hàm bên trong có tên với tham số bộ đếm để lặp lại cho bạn
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'))
Dừng đọc ở đây nếu bạn không thích học thêm nhiều thứ ...
Nhưng một cái gì đó nên cảm thấy về những ...
if
báo cáo chi nhánh duy nhất là xấu xí - điều gì xảy ra trên các chi nhánh khác?
- nhiều câu lệnh / biểu thức trong các cơ quan chức năng - các mối quan tâm về thủ tục có bị trộn lẫn không?
- ngầm trả lại
undefined
- dấu hiệu của tạp chất, chức năng phụ
"Không có cách nào tốt hơn à?"
Có. Trước tiên hãy xem lại triển khai ban đầu của chúng tôi
// times :: Int -> (void -> void) -> void
const times = x => f => {
if (x > 0) {
f() // has to be side-effecting function
times (x - 1) (f)
}
}
Chắc chắn, nó đơn giản, nhưng chú ý cách chúng ta chỉ gọi f()
và không làm gì với nó. Điều này thực sự giới hạn loại chức năng chúng ta có thể lặp lại nhiều lần. Ngay cả khi chúng ta có sẵn trình lặp, thì f(i)
nó vẫn không linh hoạt hơn nhiều.
Điều gì xảy ra nếu chúng ta bắt đầu với một loại thủ tục lặp lại chức năng tốt hơn? Có lẽ một cái gì đó làm cho việc sử dụng đầu vào và đầu ra tốt hơn.
Lặp lại chức năng chung
// 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
Ở trên, chúng tôi đã định nghĩa một repeat
hàm chung có một đầu vào bổ sung được sử dụng để bắt đầu ứng dụng lặp lại của một hàm duy nhất.
// 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)))
Thực hiện times
vớirepeat
Vâng, điều này bây giờ dễ dàng; gần như tất cả các công việc đã được thực hiện.
// 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'))
Vì hàm của chúng ta lấy i
làm đầu vào và trả về i + 1
, nên hàm này hoạt động hiệu quả như là trình lặp của chúng ta mà chúng ta chuyển đếnf
mỗi lần.
Chúng tôi cũng đã sửa danh sách các vấn đề của mình
- Không còn chi nhánh xấu xí
if
báo cáo
- Các cơ quan biểu hiện đơn biểu thị mối quan tâm riêng biệt
- Không còn vô dụng, hoàn toàn trở lại
undefined
Toán tử dấu phẩy,
Trong trường hợp bạn gặp khó khăn khi xem ví dụ cuối hoạt động như thế nào, thì nó phụ thuộc vào nhận thức của bạn về một trong những trục chiến đấu lâu đời nhất của JavaScript; các toán tử dấu phẩy - trong ngắn hạn, nó đánh giá biểu thức từ trái sang phải và trả về giá trị của biểu thức đánh giá cuối cùng
(expr1 :: a, expr2 :: b, expr3 :: c) :: c
Trong ví dụ trên của chúng tôi, tôi đang sử dụng
(i => (f(i), i + 1))
đó chỉ là một cách viết ngắn gọn
(i => { f(i); return i + 1 })
Tối ưu hóa cuộc gọi đuôi
Cũng hấp dẫn như các triển khai đệ quy, tại thời điểm này, tôi sẽ vô trách nhiệm khi đề xuất với họ rằng không có JavaScript VM nào tôi có thể nghĩ đến việc hỗ trợ loại bỏ cuộc gọi đuôi thích hợp - babel đã sử dụng để phiên dịch nó, nhưng nó đã bị "phá vỡ; "Tình trạng trong hơn một năm.
repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded
Vì vậy, chúng ta nên xem lại việc thực hiện repeat
để làm cho nó an toàn.
Mã dưới đây không sử dụng các biến có thể thay đổi n
và x
lưu ý rằng tất cả các đột biến được định vị cho repeat
hàm - không có thay đổi trạng thái (đột biến) nào được nhìn thấy từ bên ngoài hàm
// 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
Điều này sẽ có rất nhiều bạn nói "nhưng đó không phải là chức năng!" - Tôi biết, cứ thư giãn đi. Chúng ta có thể thực hiện giao diện loop
/ kiểu Clojure recur
cho vòng lặp không gian không đổi bằng cách sử dụng các biểu thức thuần túy ; không có những while
thứ đó
Ở đây chúng tôi trừu tượng hóa while
với loop
chức năng của chúng tôi - nó tìm kiếm một recur
loại đặc biệt để giữ cho vòng lặp chạy. Khi không recur
gặp loại, vòng lặp kết thúc và kết quả tính toán được trả về
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