mileOfSunshine / blog

2 stars 0 forks source link

Javascript函数式编程 #11

Open mileOfSunshine opened 5 years ago

mileOfSunshine commented 5 years ago

第一章

提示(错误、警告、通知)信息的抽象

function fail (thing) {
  throw new Error(thing)
}

function warn (thing) {
  console.log(["WARNING:", thing].join(' '))
}

function note (thing) {
  console.log(["NOTE:", thing].join(' '))
}

定义事物的存在 existy

Javascript中有两个值表示不存在——null 和 undefined

function existy (x) {
  return x != null
}

判断一个对象是否应该被认为是true的同义词

function truthy (x) {
  return (x !== false) && existy(x)
}

在某个条件为真的情况下执行某个操作,否则返回undefined

function doWhen (cond, action) {
  if (truthy(cond)) {
    return action()
  } else {
    return undefined
  }
}

nth

// 判断是否是索引的数据类型.判断某个数据是否是字符串或数据
function isIndexed (data) {
  return _.isArray(data) || _.isString(data)
}
// 获取指定索引的值
function nth (a, index) {
  if (!_.isNumber(index)) fail('Expected a number as the index')
  if (!isIndexed(a)) fail("Not supported on non-indexed type")
  if ((index < 0) || (index > a.length - 1)) fail("Index value is out of bounds")

  return a[index]
}

// 获取第二个索引的值
function second (a) {
  return nth(a, 1)
}
mileOfSunshine commented 5 years ago

第二章

Javascript编程方式

  1. 命令式编程

  2. 基于原型的面向对象编程

  3. 元编程

  4. 函数式编程

反转

原理:接收一个谓词,并返回一个反转谓词结果。

function complement (pred) {
  return function() {
    return !pred.apply(null, _.toArray(arguments))
  }
}

// 示例
_.filter(['a', 'b', 3, 'd'], complement(_.isNumber)) // => ['a', 'b', 'd']

cat

原理:接收一定数量的参数,并连接它们的函数。

function cat () {
  var head = _.first(arguments)
  if (existy(head)) {
    return head.concat.apply(head, _.rest(arguments))
  } else {
    return []
  }
}

// 示例
cat([1, 2, 3], [4, 5], [6, 7, 8]) // => [1, 2, 3, 4, 5, 6, 7, 8]

construct

原理: 接收一个元素和一个数组,并用cat将元素放置在数组前方。

function construct (head, tail) {
  return cat([head], _.toArray(tail))
}

// 示例
construct(42, [1, 2, 3]) // => [42, 1, 2, 3]
mileOfSunshine commented 5 years ago

第三章

mileOfSunshine commented 5 years ago

第四章

新词

重复

function repeatedly(times, fun) {
  return _.map(_.range(times, fun))
}

条件迭代

function iterateUtil(fun, check, init) {
  var ret = []
  var result = fun(init)

  while (check(result)) {
    ret.push(result)
    result = fun(result)
  }

  return ret
}

闭包规则

防止不存在的函数:fnull

原理:检查进来的参数是否是null或undefined, 如果是则用默认值来替换,然后再调用函数。

function fnull(fun) {
  var defaults = _.rest(arguments)
  return function() {
    var args = _.map(arguments, function(e, i) {
      return e != null ? e : defaults[i]
    })
    return fun.apply(null, args)
  }
}

invoker

原理:接收一个方法,并在任何给定的对象上调用它

function invoker (NAME, METHOD) {
  return function(target) {
    if (!existy(target)) fail('Must provide a target')
    var targetMethod = target[NAME]
    var args = _.rest(arguments)

    return doWhen((existy(targetMethod) && METHOD === targetMethod), function() {
      return targetMethod.apply(target, args)
    })
  }
}

checker

原理:接收一组谓词函数(返回true或false的函数),并返回一个验证函数。返回的验证函数在给定对象上执行每个谓词,并对每个返回false的谓词增加一个特殊的错误字符串到一个数组中。如果所有的谓词返回true,那么最终返回的结果是一个空数组;否则,结构为错误消息的数据。

function checker (/* validators */) {
  var validators = _.toArray(arguments)

  return function (obj) {
    return _.reduce(validators, function (errs, check) {
      if (check(obj))
        return errs
      else 
        return _.chain(errs).push(check.message).value()
    }, [])
  }
}

验证器 validator

function validator (message, fun) {
  var f = function (/* args */) {
    return fun.apply(fun, arguments)
  }

  f['message'] = message
  return f
}

// 示例
var gonnaFail = checker(validator("ZOMG!", always(false)))
gonnaFail(100) //=> ["ZOMG!"]
mileOfSunshine commented 5 years ago

第五章

多态函数

function dispatch () {
  var funs = _.toArray(arguments)
  var size = funs.length

  return function (target) {
    var ret = undefined
    var args = _.rest(arguments)

    for (var funIndex = 0; funIndex < size; funIndex++) {
      var fun = funs[funIndex]
      ret = fun.apply(fun, construct(target, args))

      if (existy(ret)) return ret
    }
    return ret
  }
}

// 示例
function stringReverse(s) {
  if (!_.isString(s)) return undefined;
  return s.split('').reverse().join('')
}

var rev = dispatch(invoker('reverse', Array.prototype.reverse), stringReverse)

rev([1,2,3]) // => [3, 2, 1]
rev('abc') // => 'cba'

消除switch case 语句调度

function isa (type, action) {
  return function (obj) {
    if (type === obj.type) {
      return action(obj)
    }
  }
}

var performCommand = dispatch(
  isa('notify', function (obj) { return notify(obj.message) }),
  isa('join', function (obj) { return changeView(obj.target) }),
  function (obj) { alert(obj.type) }
)

// 使用dispatch来扩展performCommand
var performAdminCommand = dispatch(
  isa('kill', function (obj) { return shutdown(obj.hostname) }),
  performCommand
)

// use
performAdminCommand({ type: 'kill', hostname: 'localhost' }) // => does the shutdown action
performAdminCommand({ type: 'flail' }) // => alert box pop up
performAdminCommand({ type: 'join', target: 'foo' }) // => does the changeView action

函数组合的精华:使用现有的零部件来建立新的行为,这些新行为同样也成为了已有的零部件。

柯里化

柯里化函数为每一个逻辑参数返回一个新函数。 柯里化函数逐渐返回消耗参数的函数,直到所有参数耗尽。

推荐: 从最右边的参数向左柯里化,_.curry

确定是否应该使用柯里化的通用规则是:API是否要利用高阶函数?如果答案是肯定的,那么至少一个参数的柯里化函数是合适的。

柯里化与部分应用

关系:柯里化函数在FUN运行之前需要三次级联调用(例如curried(3)(2)(1)),而部分应用函数已准备好调用,只需要一次带两个参数的调用(例如partially(2, 3))

部分应用任意数量的参数

原理:接收一个函数和一定数量的参数并返回一个固定的第一个参数的函数。

function partial (fun /*, pargs*/) {
  var pargs = _.rest(argments)

  return function (/* arguments */) {
    var args = cat(pargs, _.toArray(arguments))
    return fun.apply(fun, args)
  }
}

前置和后置条件之间的关系:给出函数能处理的数据,那么就能确保符合条件的结果。 一切后置条件的失败永远都是函数的提供者的错 利用 _.compose 组合前置条件和后置条件

组合函数 _.compose

原理:接收一定数量的函数并将它们从右至左拼接。

mileOfSunshine commented 5 years ago

第六章

自递归

规则:

用递归遍历图

var influence = [
  ['Lisp', 'Smalltalk'],
  ['Lisp', 'Scheme'],
  ['Smalltalk', 'Self'],
  ['Scheme', 'JavaScript'],
  ['Scheme', 'Lua'],
  ['Self', 'Lua'],
  ['Self', 'JavaScript']
]

function nexts (graph, node) {
  if (_.isEmpty(graph)) return []
  var pair = _.first(graph)
  var from = _.first(pair)
  var to = second(pair)
  var more = _.rest(graph)

  if (_.isEqual(node, from)) {
    return construct(to, nexts(more, node))
  } else {
    return nexts(more, node)
  }
}

// 示例
nexts(influence, 'Lisp') // => ['Smalltalk', 'Scheme']

深度优先遍历

原理: 优先访问最左侧分支, 然后再访问右侧分支上的节点.

function depthSearch (graph, nodes, seen) {
  if (_.isEmpty(nodes)) return rev(seen) // 结果反转

  var node = _.first(nodes)
  var more = _.rest(nodes)

  if (_.contains(seen, node)) {
    return depthSearch(graph, more, seen)
  } else {
    return depthSearch(
      graph,
      cat(nexts(graph, node), more),
      construct(node, seen)
    )
  }
}

递归 pk 尾递归


// 递归
function myLength (ary) {
  if (_.isEmpty(ary)) return 0
  else return 1 + myLength(_.rest(ary))
}

// 尾递归
function tcLength (ary, n) {
  var len = n ? n : 0

  if (_.isEmpty(ary)) return len
  else return tcLength(_.rest(ary), len + 1)
}

andify

function andify (/* preds */) {
  var preds = _.toArray(arguments)

  return function (/* args */) {
    var args = _.toArray(arguments)
    var everything = function (ps, truth) {
      if (_.isEmpty(ps))
        return truth
      else
        return _.every(args, _.first(ps)) && everything(_.rest(ps), truth)
    }
    return everything(preds, true)
  }
}

// 示例
var evenNums = andify(_.isNumber, isEven)
evenNums(1, 2) // => false
evenNums(2, 4, 6, 8) // => true

orify

function orify(/* preds */) {
  var preds = _.toArray(arguments)

  return function (/* args */) {
    var args = _.toArray(arguments)

    var something = function(ps, truth) {
      if (_.isEmpty(ps))
        return truth
      else
        return _.some(args, _.first(ps)) || something(_.rest(ps), truth)
    }
    return something(preds, false)
  }
}

// 示例
var zeroOrOdd = orify(isOdd, zero)
zeroOrOdd() // => false
zeroOrOdd(0, 2, 4, 6) // => true

将数字展平的函数

function flat (array) {
  if (_.isArray(array)) {
    return cat.apply(cat, _.map(array, flat))
  else
    return [array]
  }
}

递归深克隆

function deepClone (obj) {
  if (!existy(obj) || !_.isObject(obj)) {
    return obj
  }

  var temp = new obj.constructor()
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      temp[key] = deepClone(obj[key])
    }
  }
  return temp
}

// 示例
var x = [{ a: [1, 2, 3], b: 42 }, { c: { d: [] } }]
var y = deepClone(x)
_.isEqual(x, y) // => true
y[1]['c']['d'] = 42
_.isEqual(x, y) // => false

扁平化

trampoline 所做的是不断调用函数的返回值,直到它不再是一个函数。

function trampoline (fun /*, args */) {
  var result = fun.apply(fun, _.rest(arguments))

  while (_.isFuntion(result)) {
    result = result()
  }

  return result
}

生成器

function generator (seed, current, step) {
  return {
    head: current(seed),
    tail: function () {
      console.log('forced')
      return generator(step(seed), current, step)
    }
  }
}

function genHead (gen) { return gen.head }
function genTail (gen) { return gen.tail() }

利用蹦床实现存取函数genTake

缺陷:尾部元素在被访问之前都不会被计算,也意味着每次访问都需要重新计算一次。

function genTake (n, gen) {
  var doTake = function (x, g, ret) {
    if (x === 0) {
      return ret
    } else {
      return partial(doTake, x-1, genTail(g), cat(ret, genHead(g)))
    }
    return trampoline(doTake, n, gen, [])
  }
}

// 示例
genTake(10, ints) // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
mileOfSunshine commented 5 years ago

第七章

幂等性

含义:执行无数次后还具有相同的效果。

mileOfSunshine commented 5 years ago

第八章

mileOfSunshine commented 5 years ago

第九章