函数式点滴--高阶函数及抽象

介绍

高阶函数, 即higher-order function(HOF), 接受函数为参数或返回函数的函数

javascript中函数是一等公民(first-class),意味着它可以

  • 用变量命名
  • 提供给函数作为参数
  • 由函数作为结果返回 ..

其实我们平时已经不知不觉地在使用它了,比如map, reduce, filter...

这里, 就想聊一个例子...

一个求平方根的例子

小姐姐: 求平方根? Math.sqrt
我: 额,这里想说的是,不使用Math.sqrt
小姐姐: 你想说什么?
我: 小姐姐,我想...
小姐姐: 你个老男人,年级比我还大,还天天叫我小姐姐,不行,你得请我喝奶茶
我: 额,我们一起学完sqrt,再说吧...

牛顿迭代

先准备一点数学知识, 牛顿迭代法(别名如牛顿-拉夫逊), 通俗理解

newton1

假设f(x) = 0在x0附近有一个根,那么使用上面的迭代式,依次计算x1, x2, x3.... 最终将无线逼近方程的根(如果迭代是收敛的)

再通过一个动图感受一下

newton

感兴趣的同学可以浏览 如何通俗易懂地讲解牛顿迭代法

第一个sqrt

好了,我们现在不就是想求√x的值嘛, 即 y = √x

两边平方后,其实就是求方程y^2 - x = (y - √x)(y + √x) = 0的一个正根

令f(y) = y^2 - x 对y求导f'(y) =2y

应用牛顿迭代公式

sqrt1

好了,我们可以看下√2的计算过程,

猜测 平均值
1 2/1 = 2 (2 + 1) / 2 = 1.5
1.5 2 / 1.5 = 1.33333 (1.33333 + 1.5) / 2 = 1.4167
1.4167 2 / 1.4167 = 1.418 (1.4167 + 1.418) / 2 = 1.4142
1.4142 ... ...

写代码吧

function sqrt(x, guess = 1.0) {
  // 如果guess足够好,则停止计算
  if (goodEnough(guess, x)) {
    return guess
  } else {
    return sqrt(x, improve(guess, x))
  }
}

// 改进guess值
function improve(guess, x) {
  return average(guess, x / guess)
}

// 这里我们认为 |guess^2 - x| < 0.001 即guess已经足够好
function goodEnough(guess, x) {
  return abs(square(guess) - x) < 0.001
}

function average(num1, num2) {
  return (num1 + num2) / 2
}

function square(num) {
  return num * num
}

function abs(num) {
  if (num >= 0) {
    return num
  } else {
    return 0 - num
  }
}

let r = sqrt(9)
// 3.00009155413138
console.log(r)
复制代码

第二个sqrt

再来一点数学知识, 找出函数的不动点 数x称为f的不动点,如果x满足方程f(x) = x

思路: 某些函数,通过从某个初始猜测出发,反复第应用f, f(x), f(f(x)), f(f(x)) .... 直到值变化不大时,就可以找到它的一个不动点

这里fixedPoint函数接收函数为参数,所以它是一个高阶函数

const toleracne = 0.0001

function abs(num) {
  if (num >= 0) {
    return num
  } else {
    return 0 - num
  }
}

function closeEnough(v1, v2) {
  return abs(v1 - v2) < toleracne
}

function tryGuess(f, guess) {
  let next = f(guess)
  if (closeEnough(guess, next)) {
    return next
  } else {
    return tryGuess(f, next)
  }
}

function fixedPoint(f, firstGuess = 1.0) {
  return tryGuess(f, firstGuess)
}

// 我们来求余弦函数的不动点
// r1为0.7390822985224023
let r1 = fixedPoint(Math.cos)

// 再来验证一下
// 发现r2为0.7390870426953322
let r2 = Math.cos(0.7390822985224023)
复制代码

现在我们来将平方根的计算形式化为一个寻找不动点的计算过程
计算y = √x, 就是找到一个y使得y = x^2, 其等价于 y = x / y, 所以我们只需寻找 y = x / y的不动点

遗憾的是,搜寻并不收敛。考虑初始猜测y1, 下一猜测y2 = x / y1, 而下一猜测y3 = x / y2 = y1。出现了往复震荡
采用"平均阻尼"的技术,帮助收敛,取y之后的下一猜测值为 (1/2)(y + x / y),即寻找 y = (1/2)(y + x / y)的不动点

function sqrt(x) {
  return fixedPoint((y) => {
    return average(y, x/y)
  })
}
复制代码

而"平均阻尼"也是常用到的一个技术,所以我们也定义一下

它接收一个函数为参数,返回另一个函数

function averageDamp(f) {
  return (x) => {
    return average(x, f(x))
  }
}
复制代码

利用averageDamp重做平方根

function sqrt(n) {
  return fixedPoint(averageDamp((y) => {
    return n / y
  }))
}
复制代码

第三个sqrt

回到前面的牛顿迭代法。如果g(x)是可微的,那么g(x) = 0的一个解就是f(x)的一个不动点, 其中f(x) = x - g(x) / g'(x)

现在我们将牛顿法也定义为一个函数, 先定义下导数

deriv

const dx = 0.0001

function deriv(g) {
  return (x) => {
    return (g(x + dx) - g(x)) / dx
  }
}
复制代码

现在牛顿法就可以表述为一个求不动点的过程了

function newtonTransform(g) {
  return (x) => {
    return x - g(x) / deriv(g)(x)
  }
}

function newtonMethod(g, guess = 1.0) {
  return fixedPoint(newtonTransform(g), guess)
}
复制代码

于是,sqrt可以定义为

function sqrt(x) {
  return newtonMethod((y) => {
    return square(y) - x
  })
}
复制代码

抽象

其实上面描述平方根,用了两种方式,一种方式是使用不动点搜寻,一种方式是使用牛顿法。而牛顿法本身也是一个不动点的计算过程。

所以我们将这也抽象成一个函数

function fixedPointOfTransform(g, transform, guess = 1.0) {
  return fixedPoint(transform(g), guess)
}
复制代码

那么第二个sqrt可以更改为

function sqrt(x) {
  return fixedPointOfTransform((y) => {
    return x / y
  }, averageDamp)
}
复制代码

那么第三个sqrt可以更改为

function sqrt(x) {
  return fixedPointOfTransform((y) => {
    return square(y) - x
  }, newtonTransform)
}
复制代码

小结

我: 走吧,去买奶茶
小姐姐: 不去了,头疼...

今天通过一个例子,我们一起了解了下高阶函数,以及使用高阶函数进行抽象的过程

猜你喜欢

转载自juejin.im/post/5b6837a751882561ef445c62
今日推荐