ตกลง!
โค้ดด้านล่างนี้เขียนโดยใช้ไวยากรณ์ ES6 แต่สามารถเขียนได้อย่างง่ายดายใน ES5 หรือแม้แต่น้อย ES6 ไม่ใช่ข้อกำหนดในการสร้าง "กลไกการวนรอบ x ครั้ง"
หากคุณไม่ต้องการตัววนซ้ำในการติดต่อกลับนี่เป็นการใช้งานที่ง่ายที่สุด
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'))
หากคุณต้องการตัววนซ้ำคุณสามารถใช้ฟังก์ชัน inner ที่มีชื่อพร้อมพารามิเตอร์ตัวนับเพื่อทำซ้ำให้กับคุณ
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'))
หยุดอ่านที่นี่ถ้าคุณไม่ชอบการเรียนรู้สิ่งต่าง ๆ มากขึ้น ...
แต่สิ่งที่ควรรู้สึกเกี่ยวกับสิ่งเหล่านั้น ...
if
คำสั่งสาขาเดียวน่าเกลียด - เกิดอะไรขึ้นกับสาขาอื่น
- มีหลายข้อความ / นิพจน์ในส่วนของฟังก์ชั่น - มีความกังวลเกี่ยวกับกระบวนการหรือไม่?
- กลับมาโดยปริยาย
undefined
- บ่งบอกถึงการไม่บริสุทธิ์ฟังก์ชั่นผลข้างเคียง
"ไม่มีทางที่ดีกว่านี้อีกเหรอ?"
นั่นคือ ก่อนอื่นมาทบทวนการใช้งานครั้งแรกของเรา
// times :: Int -> (void -> void) -> void
const times = x => f => {
if (x > 0) {
f() // has to be side-effecting function
times (x - 1) (f)
}
}
แน่นอนมันง่าย แต่สังเกตว่าเราเพิ่งโทรมาf()
และไม่ทำอะไรกับมัน นี่เป็นการ จำกัด ประเภทของฟังก์ชั่นที่เราสามารถทำซ้ำได้หลายครั้ง แม้ว่าเราจะมีตัววนซ้ำให้ใช้งาน แต่f(i)
ก็ไม่สามารถใช้งานได้หลากหลาย
ถ้าเราเริ่มต้นด้วยขั้นตอนการทำซ้ำฟังก์ชั่นที่ดีกว่า บางทีสิ่งที่ทำให้การใช้อินพุทและเอาท์พุทดีขึ้น
การทำซ้ำฟังก์ชั่นทั่วไป
// 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
ด้านบนเรากำหนดrepeat
ฟังก์ชันทั่วไปซึ่งรับอินพุตเพิ่มเติมซึ่งใช้เพื่อเริ่มต้นแอปพลิเคชันซ้ำของฟังก์ชันเดียว
// 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)))
ดำเนินการtimes
ด้วยrepeat
อย่างนี้ง่ายตอนนี้; งานเกือบทั้งหมดได้ทำไปแล้ว
// 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'))
เนื่องจากฟังก์ชั่นของเราใช้i
เป็นอินพุตและส่งคืนi + 1
สิ่งนี้จะทำงานได้อย่างมีประสิทธิภาพเป็นตัววนซ้ำของเราที่เราส่งไปf
แต่ละครั้ง
เราได้แก้ไขรายการหัวข้อย่อยของปัญหาด้วย
- ไม่มี
if
งบสาขาเดียวที่น่าเกลียดอีกต่อไป
- เนื้อความแบบนิพจน์เดียวบ่งบอกถึงความกังวลที่แยกจากกันอย่างชัดเจน
- ไม่มีประโยชน์มากขึ้นกลับโดยปริยาย
undefined
ตัวดำเนินการจุลภาคของจุลภาค,
ในกรณีที่คุณมีปัญหาในการดูว่าตัวอย่างล่าสุดทำงานอย่างไรนั้นขึ้นอยู่กับการรับรู้ของคุณในหนึ่งในแกนต่อสู้ที่เก่าแก่ที่สุดของ JavaScript ตัวดำเนินการคอมม่า - ในระยะสั้นจะประเมินนิพจน์จากซ้ายไปขวาและส่งคืนค่าของนิพจน์ที่ประเมินล่าสุด
(expr1 :: a, expr2 :: b, expr3 :: c) :: c
ในตัวอย่างข้างต้นเราใช้
(i => (f(i), i + 1))
ซึ่งเป็นเพียงวิธีการเขียนรวบรัด
(i => { f(i); return i + 1 })
การเพิ่มประสิทธิภาพการโทรหาง
ณ จุดนี้มันจะไม่มีความรับผิดชอบสำหรับฉันที่จะแนะนำพวกเขาเนื่องจากไม่มีJavaScript VM VM ที่ฉันสามารถคิดว่ารองรับการกำจัดหางอย่างเหมาะสม - Babel เคยใช้ transpile แต่ก็อยู่ในสภาพ "เสียแล้วจะนำกลับมาใช้ใหม่" "สถานะดีเกินปี
repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded
ดังนั้นเราควรกลับไปใช้การดำเนินการrepeat
เพื่อให้ปลอดภัย
รหัสด้านล่างนี้ใช้ตัวแปรที่เปลี่ยนแปลงได้n
และx
โปรดทราบว่าการกลายพันธุ์ทั้งหมดจะถูกแปลเป็นภาษาท้องถิ่นไปยังrepeat
ฟังก์ชั่น - ไม่มีการเปลี่ยนแปลงสถานะ (การกลายพันธุ์) ที่สามารถมองเห็นได้จากนอกฟังก์ชั่น
// 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
สิ่งนี้จะมีคุณหลายคนพูดว่า "แต่นั่นไม่สามารถใช้งานได้!" - ฉันรู้แค่พักผ่อน เราสามารถใช้ Clojure สไตล์loop
/ recur
อินเตอร์เฟซสำหรับคงพื้นที่การวนลูปโดยใช้การแสดงออกบริสุทธิ์ ; ไม่มีwhile
สิ่งนั้น
ที่นี่เราสรุปwhile
ไปกับloop
ฟังก์ชั่นของเรา- มันมองหารูปแบบพิเศษrecur
เพื่อให้วงวิ่งต่อไป เมื่อไม่recur
พบประเภทวนรอบจะเสร็จสิ้นและผลลัพธ์ของการคำนวณจะถูกส่งกลับ
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