函数式编程 - 容器(container)

最近一直在学习函数式编程,前面介绍了函数式编程中很是重要的两个运算函数柯里化函数组合,下文出现的currycompose函数能够从前两篇文章中找到。它们均可以直接在实际开发中用到,写出函数式的程序。javascript

本文主要初探容器的相关概念,以及如何处理编程中异步操做错误处理等依赖外部环境状态变化的状况,,html

容器(container)

容器能够想象成一个瓶子,也就是一个对象,里面能够放各类不一样类型的值。想一想,瓶子还有一个特色,跟外界隔开,只有从瓶口才能拿到里面的东西;类比看看, container 回暴露出接口供外界操做内部的值。java

一个典型的容器示例:git

var Container = function(x) {
        this.__value = x;
    }
    
    Container.of = function(x) {
        return new Container(x);
    }
    
    Container.of("test")   
    // 在chrome下会打印出 
    // Container {__value: "test"}

咱们已经实现了一个容器,而且实现了一个把值放到容器里面的 Container.of方法,简单看,它像是一个利用工厂模式建立特定对象的方法。of方法正是返回一个container。web

函子(functor)

上面容器上定义了of方法,functor的定义也相似chrome

Functor 是实现了 map函数并遵照一些特定规则的容器类型。

把值留在容器中,只能暴露出map接口处理它。函子是很是重要的数据类型,后面会讲到各类不一样功能的函子,对应处理各类依赖外部变量状态的问题。编程

Container.prototype.map = function(f) {
    return Container.of(f(this.__value))
}

把即将处理容器内变量的函数,包裹在map方法里面,返回的执行结果也会是一个Container。
这样有几点好处:segmentfault

  1. 保证容器内的value一直不会暴露出去,
  2. 对value的操做方法最终会交给容器执行,能够决定什么时候执行。
  3. 方便链式调用
// 利用上一篇中讲到的柯里化,就能够看出其特性。
    var add2 = function(x, y) {
        return x + y;
    };
    
    curriedAdd = curry(add2);
    
    Container.of(2).map(curriedAdd(3));
    // Container {__value: 5}

不一样类型的函子

maybe

容器在处理内部值时,常常遇到传入参数异常的状况的状况,检查value 值的合理性就很是重要。Maybe 函子保证在调用传入的函数以前,检查值是否为空。数组

var Maybe = function(x) {
  this.__value = x;
}

Maybe.of = function(x) {
  return new Maybe(x);
}

Maybe.prototype.isNothing = function() {
  return (this.__value === null || this.__value === undefined);
}

Maybe.prototype.map = function(f) {
  return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value));
}

这个通用的例子,体现了输出结果的不肯定性,也能够看出,在容器内部全部对value值的操做,都会交给容器来执行。在value 为空的状况下,也会返回包裹着null的容器,避免后续容器链式调用报错。dom

异常捕获函子

一般 使用throw/catch就能够捕获异常,抛出错误,但它并非一种纯函数方法,最好的方法是在出现异常时,能够正常返回信息。Either函子,内部两个子类Leftright; 能够当作右值是正常状况下使用并返回的值,左值是操做简单的默认值。

var Left = function(x) {
  this.__value = x;
}

Left.of = function(x) {
  return new Left(x);
}

Left.prototype.map = function(f) {
  return this;
}

var Right = function(x) {
  this.__value = x;
}

Right.of = function(x) {
  return new Right(x);
}

Right.prototype.map = function(f) {
  return Right.of(f(this.__value));
}

// 输入数据进行校验
var setage = function(age) {
     return typeof age === 'number'? Right.of(age): Left.of('error age')
}

setage(12).map(function(age){return 'my age is' + age})
// Right {__value: "my age is12"}
setage("age").map(function(age){return 'my age is' + age})
// Left {__value: "error age"}

leftright 惟一的区别在于map 方法的实现,固然,一个函子最大的特色也体如今map方法上,
Left.map 无论传入的是什么函数,直接返回当前容器;Right.map则是示例里面的方法同样。

IO 操做

IO 操做自己就是不纯的操做,生来就得跟外界环境变量打交道,不过能够掩盖他的不肯定性。跟下面localStorage包裹函数相似,延迟执行IO 操做。

var getStorage = function(key) {
    return function() {
        return localStorage[key];
    }
}

再看看,封装了高级一点的IO 函子:

var IO = function(f) {
        this.__value = f;
    }
    
    IO.of = function(x) {
        return new IO(function(){
            return x;
        })
    }
    
    IO.prototype.map = function(f) {
        // 使用上一句定义的compose函数
        return new IO(compose(f, this.__value))
    }

compose函数组合,里面存放的都是函数,this.__value跟其余函子内部值不一样,它是函数。IO.of方法在new对象以前,把值包裹在函数里面,试图延迟执行。

// 测试一下
var io__dom= new IO(function() {return window.document})

io__dom.map(function(doc) { return doc.title})

// IO {__value: ƒ}

返回一个没有执行的函数对象,里面的__value值对应的函数,在上面函数调用后并无执行,只有在调用了this.__value值后,才执行。最后一步不纯的操做,交给了函数调用者去作。

Monad

一个functor, 只要他定义了一个join 方法和一个of 方法,那么它就是一个monad。 它能够将多层相同类型的嵌套扁平化,像剥洋葱同样。关键在于它比通常functor 多了一个join 方法。 咱们先看看剥开一层的join方法。

var IO = function(f) {
        this.__value = f
    }
    
    IO.of = function(x) {
        return new IO(function(){
            return x;
        })
    }
    
    IO.prototype.join = function() {
        return this.__value ? this.__value(): IO.of(null);
    }
    // 包裹上两层
    var foo = IO.of(IO.of('test bar'));
    foo.join().__value();
    // 返回里面嵌套着的IO类。 IO {__value: ƒ},接着只需调用这里的__value(),就能够返回字符串`test bar`;

回头看看前面map方法,return new IO(),生成新的容器,方便链式调用,跟 join方法结合一块儿使用,生成容器后,再扁平化。造成 chain 函数

var  chain = curry(function(f, m) {
        return m.map(f).join();
    })

看一个完整示例,其中currycompose,分别用到了连接里面的实现,:

var IO = function(f) {
  this.__value = f;
}

IO.of = function(x) {
  return new IO(function() {
    return x;
  })
}

IO.prototype.map = function(f) {
  // 使用上一句定义的compose函数
  return new IO(compose(f, this.__value))
}

IO.prototype.join = function() {
  return this.__value ? this.__value() : IO.of(null);
}

var chain = curry(function(f, m) {
  return m.map(f).join();
})

var log = function(x) {
  return new IO(function() {
    console.log(x);
    return x;
  })
}

var setStyle = curry(function(sel, props) {
  return new IO(function() {
    return document.querySelector(sel).style.background = props
  })
})

var getItem = function(key) {
  return new IO(function() {
    return localStorage.getItem(key);
  })
};

var map = curry(function(f, functor) {
  return functor.map(f);
});

// 简单实现join
var join = function(functor) {
  return functor.join();
}

localStorage.background = '#000';

var setItemStyle = compose(join, map(setStyle('body')), join, map(log), getItem);

// 换成 链式调用。
setItemStyle = compose(chain(setStyle('body')), chain(log), getItem);

setItemStyle('background').__value(); // 操做dom 改变背景颜色

总结

本文主要利用简单代码举例,介绍了容器,函子等相关概念,初步认识了各类不一样的函子。深刻实践示例,能够参考阅读下面连接:

  1. 函数式编程风格
  2. js函数式编程指南https://llh911001.gitbooks.io...
  3. JavaScript函数式编程(二)
  4. JavaScript:函数式编程基本概念学习
  5. JS函数式编程 - 函子和范畴论
  6. javascript函数式编程之 函子(functor)
  7. 函数式编程入门教程
相关文章
相关标签/搜索