확인!
아래 코드는 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