확인!
아래 코드는 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의 가장 오래된 전투 축 중 하나에 대한 인식에 달려 있습니다. 쉼표 연산자 - 짧은에, 그것은 왼쪽에서 오른쪽으로 식을 평가하고 반환 마지막 평가 식의 값을
(expr1 :: a, expr2 :: b, expr3 :: c) :: c
위의 예에서 저는
(i => (f(i), i + 1))
간결한 글쓰기 방식입니다
(i => { f(i); return i + 1 })
테일 콜 최적화
재귀 적 구현만큼 섹시하지만이 시점에서 자바 스크립트 VM이 적절한 꼬리 호출 제거를 지원할 수 있다고 생각할 수 없기 때문에 권장하는 것은 무책임 할 것입니다. "1 년 이상 지위.
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