Skip to content

fp tips

Posted on:February 8, 2022

Table of contents

Open Table of contents

关于 memoize

// 这里使用了数组作为缓存,特化于当前用例 ;
// 对于更加一般的情况,可以使用对象作为缓存 ,
// 以序列化后的函数参数作为 key , 计算结果作为 value ,
// 同时由于序列化带来的性能损失, 可以对参数是否为 primitive
// 作判断, 来避免不必要的序列化
//
// 参考: https://blog.risingstack.com/the-worlds-fastest-javascript-memoization-library/
const cache: number[] = []

const fib = (n: number) => {
  if (!cache[n]) {
    if (n <= 1) {
      cache[n] = n
    } else {
      cache[n] = fib(n - 1) + fib(n - 2)
    }
  }

  return cache[n]
}

const unMemoized = (n: number): number => {
  if (n == 0 || n == 1) {
    return n
  } else {
    return unMemoized(n - 1) + unMemoized(n - 2)
  }
}

注入的例子:

type LogOption = {
  preInvoke: Console['log']
  startInvoke: Console['log']
  postInvoke: Console['log']
}

const predefinedLogOption = {
  preInvoke: console.log,
  startInvoke: console.log,
  postInvoke: console.log,
}

// 用于打印的函数属于 impure functions,
// 这里通过外部注入, 便于组合于测试
const logWrapper = <T extends (...args: any[]) => any>(
  fn: T,
  logOption: LogOption = predefinedLogOption // injecting impure functions
) => {
  return function (...args: Parameters<T>): ReturnType<T> {
    logOption.preInvoke(`${fn.name} called`)
    logOption.startInvoke(`arguments are: `, ...args)

    const result = fn(...args)

    logOption.postInvoke(`result is `, result)

    return result
  }
}

const measureWrapper = <T extends (...args: any[]) => any>(fn: T) => {
  return function (...args: Parameters<T>): ReturnType<T> {
    const t0 = performance.now()

    const result = fn(...args)

    const t1 = performance.now()

    console.log(`took ${t1 - t0} millseconds`)

    return result
  }
}

logWrapper(measureWrapper(fib))(1000)

impure 与 pure

// 此外,关于如何将 impure function 转换为 pure function ,
// 通过 delay evaluation 的方式来实现
// 比如, 网络请求函数, 可以这么写:
const fetcher = (url: string) => () => fetch(url)
// 由于每次都返回同样的函数, 所以是 pure 的

// 大费周章都要转换为 pure function 的目的是什么呢,
// 有以下几点:
// 1. 便于组合
// 2. 便于测试
// 3. 便于缓存
// 4. 便于调试
// 5. 便于维护

// 利用 bind 预置参数, 可以实现柯里化
const curry = (fn: (...args: any[]) => any) => {
  return fn.length === 0 ? fn() : (p: any) => curry(fn.bind(null, p))
}

const sum = (x: number, y: number, z: number) => x + y + z
const curriedSum = curry(sum)
console.log(curriedSum(3)(40)(5))

// 柯里化的简单应用
const ImageExts = ['.jpg', '.png', '.jpeg', '.webp']

const isParticularExt = (extList: string[]) => (name: string) => {
  return extList.some(ext => name.endsWith(ext))
}

const isImageExt = isParticularExt(ImageExts)

console.log(isImageExt('dog.jpg'), isImageExt('haha.mp4'))

oop 中设计模式的等效版本

1. decorator 与 wrapper

化为高阶函数, 以 react 中高阶组件举例:

import * as React from 'react'

const FullNameDisplay = (props: { firstName: string; lastName: string }) => {
  return (
    <div>
      {props.firstName} {props.lastName}
    </div>
  )
}

// 想要附加一个边框的 feature, 可以这么写
const MakeVisible = (Component: ReactNode) => {
  return <div style={{ border: '1px solid red' }}>{Component}</div>
}

Containers

1. 为何需要 containers

boolean, number, string 等基本数据类型不够灵活, 以及对其的操作没有统一的接口, 具体来说, 在 promise 中 , 我们可以用 then 不停地链式调用, 也就是在每次 then 后返回的都是 promise ( 同样的 containers ) 。

2. 基本的 container --- functor

class Functor<T> {
  private _value: T

  constructor(init: T) {
    this._value = init
  }

  // of 方法用于创建 functor
  static of<U>(init: U) {
    return new Functor(init)
  }

  // map 方法用于映射 functor 的值
  map<U>(fn: (param: T) => U) {
    return Functor.of(fn(this._value))
  }

  // 拆开盒子, 拿出盒子里的值
  value() {
    return this._value
  }
}

// 调用一下
console.log(
  Functor.of(33)
    .map(c => c + 43)
    .map(c => `${c} years`)
    .value()
)

// 结果为 "76 years"