OK!
下面的代码是使用ES6语法编写的,但也可以用ES5或更少的语法编写。ES6不要求创建“循环x次的机制”
如果回调中不需要迭代器,这是最简单的实现
Const times = x => f => {
If (x > 0) {
f ()
乘以(x - 1) (f)
}
}
//使用它
Times (3) (() => console.log('hi'))
//或为重用定义中间函数
令twice =乘以(2)
//两倍的幂!
Twice (() => console.log('double vision'))
如果确实需要迭代器,可以使用带counter形参的命名内部函数为您迭代
Const times = n => f => {
if = I => {
如果(i === n)返回
f(我)
Iter (i + 1)
}
返回iter (0)
}
Times (3) (i => console.log(i, 'hi'))
如果你不想学习更多的东西,请停止阅读这里…
但是有些东西应该让人感觉不舒服……
如果语句是丑陋的单个分支-在另一个分支上发生了什么?
函数体中的多个语句/表达式——是否混合了过程关注点?
隐式返回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. Int -> (a -> a
常量repeat = n => f => x => {
If (n > 0)
返回(n - 1) (f) (f (x))
其他的
返回x
}
// power:: Int -> Int -> Int
Const power = base => exp => {
//重复<exp>次,<base> * <x>,从1开始
返回repeat (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)))
重复执行时间
现在这很容易了;几乎所有的工作都已经完成了。
// repeat:: forall a. Int -> (a -> a) -> a -> a. Int -> (a -> a
常量repeat = n => f => x => {
If (n > 0)
返回(n - 1) (f) (f (x))
其他的
返回x
}
// times:: Int -> (Int -> Int) -> Int
Const times = n=> f=>
重复(n) (i => (f(i), i + 1)) (0)
//使用它
Times (3) (i => console.log(i, 'hi'))
由于我们的函数接受i作为输入并返回i + 1,因此它有效地作为我们每次传递给f的迭代器。
我们也修正了我们的项目列表问题
不再有丑陋的单个分支if语句
单一表达式体表示良好分离的关注点
没有更多无用的,隐式返回未定义
JavaScript逗号运算符
如果你看不清最后一个例子是如何工作的,这取决于你对JavaScript最古老的战斧之一的认识;逗号操作符——简而言之,它从左到右计算表达式,并返回最后求值的表达式的值
(expr1 :: a, expr2 :: b, expr3 :: c) :: c
在上面的例子中,我使用
(i => (f(i), i + 1))
这是一种简洁的写法吗
(i => { f(i); return i + 1 })
尾部呼叫优化
尽管递归实现很吸引人,但在这一点上,我推荐它们是不负责任的,因为我能想到的JavaScript VM都不支持正确的尾部调用消除- babel用来编译它,但它已经在“broken;将在一年多的时间里重新实施“状态”。
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 =>
{
令m = 0, acc = x
当(m < n)
(m = m + 1, acc = f (acc))
返回acc
}
// inc:: Int -> Int
Const inc = x =>
X + 1
Console.log (repeat (1e8) (inc) (0))
/ / 100000000
你们很多人都会说"那根本没用" -我知道,放松点。我们可以使用纯表达式实现clojure风格的循环/递归接口,用于常量空间循环;没有那些while之类的东西。
这里我们对循环函数进行了抽象——它寻找一个特殊的递归类型来保持循环运行。当遇到非递归类型时,循环结束并返回计算结果
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