ГАРАЗД!
Код нижче написаний за допомогою синтаксисів 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'))
Якщо вам потрібен ітератор , ви можете використовувати іменовану внутрішню функцію з параметром лічильника для ітерації для вас
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, the
Якщо у вас виникають проблеми з переглядом того, як працює останній приклад, це залежить від вашої обізнаності про одну з найстаріших бойових осей JavaScript; оператор коми - коротше кажучи, він обчислює вирази зліва направо і повертає значення останнього обчисленого виразу
(expr1 :: a, expr2 :: b, expr3 :: c) :: c
У нашому вище прикладі я використовую
(i => (f(i), i + 1))
що просто стислий спосіб написання
(i => { f(i); return i + 1 })
Оптимізація виклику хвоста
Настільки ж сексуально, як і рекурсивні реалізації, на даний момент для мене було б безвідповідально рекомендувати їх, враховуючи, що жоден JavaScript VM, який я можу придумати, підтримує належне усунення хвостових викликів - вавило використовується для його транпіляції, але це було "зламано; буде повторно "статус понад рік.
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