内容简介:当我们说函数是“一等公民”的时候,我们实际上说的是它们跟其他对象都一样,你可以像对待其他数据结构一样对待它们。相同的输入产生相同的输出,并且没有副作用,即为纯函数。例如只要函数跟外界环境发生交互,就是有副作用。但并不是说要禁止副作用,函数式的编程哲学是假定副作用是造成不正当行为的主要原因。
当我们说函数是“一等公民”的时候,我们实际上说的是它们跟其他对象都一样,你可以像对待其他数据结构一样对待它们。
纯函数
相同的输入产生相同的输出,并且没有副作用,即为纯函数。例如 Array.prototype.slice
是纯函数,而 Array.prototype.splice
不是纯函数。
只要函数跟外界环境发生交互,就是有副作用。但并不是说要禁止副作用,函数式的编程哲学是假定副作用是造成不正当行为的主要原因。
纯函数的好处:
- 可缓存性 Cacheable
- 可移植性 Portable
- 可测试性 Testable
- 合理性 Reasonable (引用透明)
柯里化
只传递一个参数来调用它,然后返回另一个函数处理剩下的参数,称为柯离化,也叫做局部调用。
const _ = require('lodash/fp') const add = _.curry((a, b) => a + b) const add2 = add(2) add2(3) // 5
组合
通过组合两个或多个函数返回一个新的函数,例如:
const g = n => n + 1; const f = n => n * 2; const fn = compomse(f, g); fn(1); // => f(g(1)) => 4
在 compomse 中,g 将先于 f 执行,因此创建了一个从右到左的数据流。
结合律:compomse(f, compomse(g, h)) 等同于 compomse(compomse(f, g), h)
pointfree
“Love means never having to say you're sorry”
pointfree 模式是指永远不需要声明数据。
以一个不恰当的代码比喻:
const hello = name => console.log(`hello ${name}`); // not pointfree const sayHello = (name) => { return hello(name); } // pointfree const sayHello = hello
PS: 个人理解,pointfree 即是指函数仅只是其他函数的组合,并不需要指定外部的数据,函数中也不需要传入外部变量。
但这无法绝对避免,例如获取某个时间字符串的时间戳: const timestamp = (date) => (new Date(date)).getTime()
。
debug
在函数的组合中需要进行 debug 的话,可以使用 trace
函数。
const trace = curry((tag, value) => { console.log(tag, value); return value; });
将 trace
函数插入到 compomse
中即可检查上一个函数返回值是否正确。
identity
范畴学中独特的态射,这个函数接受随便什么东西,然后原封不动的吐出来。一个假装自己是普通数据的函数。
const identity = x => x;
identity 函数可以一起使用,但是看起来好像是没有卵用:
compomse(identity, f) == compomse(f, identity) == f
类型签名
接受具体类型,返回具体类型:
// hello::String -> String const hello = name => `hello ${name}`;
接受任意类型,并返回相同类型:
// identity::a -> a const identity = n => n
接受函数参数:
// map::(a -> b) -> ![pic](a) -> ![pic](b) const map = curry((f, xs) => xs.map(f));
类型约束
// sort::Ord a => ![pic](a) => ![pic](a) const sort = balabala
胖箭头的左边表明 a 一定是一个 Ord 对象,也就是说 a 必须要实现 Ord 接口(可排序)。
通过这种方式能够限制函数的作用范围,这种接口声明叫做类型约束。
functor
const Functor = (x) => { this.__value = x; }; Functor.of = x => new Functor(x); Functor.prototype.map = f => Functor.of(f(this.__value));
functor 是实现了 map
函数,并遵守某些特点规则的容器类型,具有 mappable
的特点(类似于 Promise 的 thenable)。
Maybe
Maybe 是另一种 functor,实际上只是多了空值检查。
const Maybe = (x) => { this.__value = x; }; Maybe.of = x => new Maybe(x); Maybe.prototype.isNothing = () => this.__value === null || this.__value === undefined; Maybe.prototype.map = f => this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value));
Maybe 使得在 map
调用中产生 null 或者 undefined 时不会爆出错误,而是最后返回 Maybe.of(null)
。
Either
Either 又是另外一种 functor, 不同于正常的 functor, Either 不管怎么 map 都不会改变自己的值。
const Either = (x) => { this.__value = x; }; Either.of = x => new Either(x); Either.prototype.map = () => this;
Either 用于错误处理,当出现错误时我们返回一个 Either.of(error)
, 它将把错误带到最后,并显示出来。
实际上它就是一个错误消息的 functor, 只是指不会被改变。
IO
IO 又又是另外一种 functor, 与最普通的 functor 的差别是,IO 的 __value
是一个函数(不过,我们把它当成是数据)。
const IO = (f) => { this.unsafaPerfromIO = f; }; IO.of = x => new IO(() => x); IO.prototype.map = f => IO.of(compomse(f, this.unsafaPerfromIO));
IO 把非纯的动作捕获到包裹函数中,延迟执行非纯的动作。并且,假装 IO 的返回指不是包裹函数本身,而是包裹函数执行后的返回值。
当需要获取 IO 的值的时候,就执行 IO.unsafaPerfromIO()
(此时才会执行整个过程 map by map)。
Monad
pointed functor 是指实现了 of
方法的 functor
monad 是可以变扁的 pointed functor
monad 主要的使用场景是用来解决嵌套的 functor。
一个 functor,只要它定义了一个 join
方法和 of
方法,并遵守一些定律,那么它就是一个 monad。
const getItem = key => IO.of(() => localStorage.getItem(key)); const log = x = IO.of(() => { console.log(x); return x; }); const printItem = compomse(join, map(log), getItem); printItem('xxx').unsafaPerfromIO();
chain 函数
chain 函数是 functor map 之后 join 的抽象行为
const chain = curry((f, m) => m.map(f).join());
PS: 其实没有啥用,只是把 compomse(join, map(log), ...)
变成了 compomse(chain(log), ...)
Applicative Functor
applicative functor 能够以一种简明扼要的方式把一个 functor 的值应用到另外一个 functor 上。
applicative functor 是实现了 ap
方法的 pointed functor
// applicative Functor.prototype.ap = other => other.map(this.__value); // Functor.of(add(2)).ap(Functor.of(3));
Functor.of(x).map(f)
等价于 Functor.of(f).ap(Functor.of(x))
。
lift
以 pointfree 的方式调用 applicative functor。
const liftA2 = curry((f, functor1, functor2) => functor1.map(f).ap(functor2));
操作符
haskell 中可以使用 <$>
表示 map
, <*>
表示 ap
。
Functor.of(2).map(add).ap(Functor.of(3)); // map(add, Functor.of(2)).ap(Functor.of(3));
等同于:
add <$> Functor 2 <*> Functor 3
定律
A.of(id).ap(v) == v A.of(f).ap(A.of(x)) == A.of(f(x)) v.ap(A.of(x)) == A.of(f => f(x)).ap(x) A.of(compomse).ap(u).ap(v).ap(w) == u.ap(v.ap(w))
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 函数式编程之数组的函数式编程
- 函数式编程 – 函数式编程如何影响您的编码风格?
- 纯函数:函数式编程入门
- 深入理解 Java 函数式编程,第 1 部分: 函数式编程思想概论
- 思想交融,Android中的函数式编程(2):什么是函数式编程
- 编程范式 —— 函数式编程入门
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。