vlambda博客
学习文章列表

使用函数式编程的几个示例

函数式编程简介

函数式编程是指为创建不可变的程序,通过消除外部可见的副作用,来对纯函数的声明式的求值过程。

函数式编程具有引用透明、存储不可变数据、无副作用的特点。其属于声明式编程范式,关注的是如何用各种表达式来控制程序逻辑。

用函数链进行序列化操作

链式操作既能减少临时变量声明,也能提高代码的可读性。

考虑一个场景,需要对一组姓名进行读取、规范化、去重,最终进行排序

输入: ['alonzo church', 'Haskell curry', 'stephen_kleene', 'John Von Neumann', 'stephen_kleene']

输出:['Alonzo Church', 'Haskell Curry', 'John Von Neumann', 'Stephen Kleene']

示例代码如下所示:

arr
  .filter(Boolean)  // 过滤掉非法值,如null, undefined, ""等
  .map(s => s.replace(/_/g' ').replace(/\b(\w)/g, p1 => p1.toUpperCase()))  // 规范化值
  .filter((it, ix, arr) => arr.indexOf(it) === ix)  // 去重
  .sort()  // 排序

使用链式调用,整个过程清晰明了,代码可读性较高。在这过程中,没有声明任何临时变量,没有产生任何的冗余代码。

柯里化的函数求值

柯里化是一种在所有参数被提供之前,挂起或延迟函数执行,将多参函数转换为一元函数序列的技术,定义为:

curry(f) :: (a,b,c) -> f(a) -> f(b) -> f(c)

以一个经典的实现不定参数的求和函数为例:

// 实现不定参数的柯里化
const curry = (fn, ...args) => {
  const f = (...nextArgs) => curry(fn, ...args, ...nextArgs)
  f.getValue = () => fn.apply(null, args)
  return f
}
// 定义累加函数
const sum = (...args) => args.reduce((total, item) => total + Number(item))
const add = curry(sum)

add(12)(3)(4)()(678)(null0.2).getValue()

函数组合、管道函数

函数组合是将若干个函数组合成一个新的函数,同时完成数据的传递。函数组合能将函数的描述与求值分离开。

对于g :: A -> Bf :: B -> C,则f.g = f(g) = compose :: (B -> C) -> (A -> B) -> (A -> C)

函数的管道化指的是以函数作为组件,将函数的输入和输出松散的连接在一起。即:

A -> f(A) -> B -> f(B) -> C

示例代码如下所示:

// 组合函数实现
const compose = (...fns) => args => fns.reduceRight((sum, fn) => fn(sum), args)

const normalize = str => str.split("-");
const dumplicate = arr => arr.filter((it, ix, array) => array.indexOf(it) === ix)
const findBiggest = arr => arr.sort((a, b) => b - a)[0]
const someFn = compose(findBiggest, dumplicate, normalize)

someFn("1-8-1-1-3-2-8-9-5-6-3")

惰性求值

惰性求值可避免不必要的函数调用,惰性求值的目的是尽可能地推迟求值,直到依赖的表达式被调用。可通过Generator模拟惰性求值,通过next操作下一步的值, next :: void -> { value, done },相关库可参考lazy.js。

// 简单实现一个惰性求值
const numbers = function* ({
  let i = 1
  while (true) {
    yield i++
  }
}

const Lazy = iterator => {
  const next = iterator.next.bind(iterator)
  const map = f => Lazy({
    ...iterator, next() => {
      const wrappedNext = next();
      return { value: f(wrappedNext.value), done: wrappedNext.done }
    }
  })
  const filter = f => Lazy({
    ...iterator, next() => {
      while (true) {
        const wrappedNext = next();
        if (f(wrappedNext.value)) return wrappedNext
      }
    }
  })
  const take = len => {
    const result = [];
    while (result.length < len) {
      result.push(next().value)
    }

    return result;
  }

  return { next, map, filter, take }
}

Lazy(numbers()).map(x => x ** 2).filter(x => x % 2 === 1).take(10)
// 结果:[1, 9, 25, 49, 81, 121, 169, 225, 289, 361]
// 运行次数:map 19次,filter 19次

尾部调用优化

尾部调用优化(TCO),指的是在尾部位置调用函数。其会使用新帧来替换当前帧,而不是将新的帧叠加在旧帧之上,从而优化函数调用栈。

示例:

// 以常见的阶乘来演示尾递归

// 常规方式
const factorail = n => n <= 1 ? 1 : n * factorail(n - 1)

// 尾递归
const factorail = (n, cur = 1) => n <= 1 ? cur : factorail(n - 1, n * cur)

关于我

本文到此结束,感谢您的阅读,我们下篇文章见。





关注前端快爆

获取更多精彩

前端快爆




点下"在看",你最好看