函數式編程

範疇與容器

    一、咱們能夠把「範疇」想象成一個容器,裏面包含兩樣東西,值(value),值的變形關係,也就是函數。
    二、範疇論使用函數,表達範疇之間的關係
    三、伴隨着範疇論的發展,就發展出一整套函數的運算方法。這套方法起初只用於數學運算,後來有人將它在計算機上實現了,就編程了今天的函數式編程
    四、本質上,函數式編程只是範疇論的計算方法,跟數學邏輯、微積分、行列式是同一個東西,都是數學方法,只是它碰巧能用來寫程序。爲何函數式編程要求函數必須是純的,不能有反作用?由於它是一種數學運算,原始目的就是求值,不作其餘事,不然就沒法知足函數運算法則了。

   範疇與容器

        一、函數不只僅能夠用於同一個範疇之中的值轉換,還能夠用於將一個範疇轉成另外一個範疇,這就涉及到了函子(functor)
        二、函子是函數式編程裏面最重要的數據類型,也是最基本的運算單位和功能單位。它首先是一種範疇,也就是說,是一個容器,包含了值和變形的關係。比較特殊的是,它的變形關係能夠依次做用於每個值,將當前容器變造成另外一個容器。
    - 容器、Functor(函子)
        $(...)返回的對象並非一個原生的DOM對象,而是對於原生對象的一種封裝,這種封裝,在某種意義上就是一個‘容器’(但它並非函數式)
        functor(函子)最守一些特定規則和容器類型。
        functor是一個對於函數調用的抽象,咱們賦予容器本身去調用函數的能力,把東西裝進一個容器,只留出一個接口map給容器外的函數,map一個函數時,咱們讓容器本身來運行這個函數    ,這樣容器就能夠本身地選擇什麼時候何地如何操做這個函數,以至於擁有惰性求值,錯誤處理,異步調用等等,很是牛的特性
  var Container=function(value){
                this.__value = value
            }
            //函數式編程通常約定,函子有一個of方法
            Container.of=(x)=>(new Container(x))//Container.of('aaa')
            
            //通常約定,函子的標誌就是容器具備map方法。該方法將容器裏的每個值,映射到另外一個容器
            Container.prototype.map=function(f){
                return Container.of(f(this.__value))
            }
            Container.of(2)
                .map(x=>x+1)//Container(3)
                .map(x=>console.log("x的值爲",x))//Container("x的值爲3")
        

  函子的代碼實現

        一、任何具備map方法的數據結構,均可以當作函子的實現。
    - map
        下面代碼,Functor是一個函子,它的map方法接受函數f做爲參數,而後返回一個新的函子,裏面包含的值是被f處理過的f(this.val)。
        通常約定,函子的標誌就是容器具備map方法。該方法將容器裏的每個值,映射到另外一個容器。
        下面的例子說明,函數式編程裏面的運算,都是經過函子完成,即運算不直接針對值,而是針對這個值的容器---函子。函子自己具備對外接口(map方法),各類函數就是運算符,經過接口接入容器,引起容器裏面的變形。
        所以,學習函數式編程,實際上就是學習函子的各類運算,因爲能夠把運算方法封裝在函數裏面,因此有衍生出各類不一樣類型的函子,有多少種運算,就有多少種函子。函數式編程就變成了運用不一樣的函子,解決實際問題。
    
          class Functor{
                constructor(val){
                    this._val=val
                }
                map(f){
                    return new Functor(f(this._val))
                }
            }
            (new Functor(3)).map(x=>x+1)//Functor(4)
  of方法
        你可能注意到了,上面生成新的函子的時候,用了new命令。這實在是太不像函數式變成了,由於new命令是面向對象函數的標誌。
        函數式編程通常約定,函子有一個of方法,用來新生成的容器。
    
        Functor.of=function(x){
            return new Functor(x)
        }
        Functor.of(2).map(x=>x+1)//Functor(3)

 

    maybe函子
        函子接受各類函數,處理容器內部的值,這裏就有一個問題,容器內部的值多是一個空值(null),而外部函數未必有處理空值的機制,若是傳入空值,極可能就會出錯。
  
        Functor.of(null).map(x=>x.toUppercase())//TypeError
        class Maybe extends Functor{
            map(f){
                return this._val?Maybe.of(f(this._val)):Maybe.of(null)
            }
        }
        Maybe.of(null).map(x=>x.toUppercase())//Maybe(null)

        var Maybe = function(val){
            this.val = val;
        }
        Maybe.of=function(x){
            return new Maybe(x)
        }
        Maybe.prototype.map=function(f){
            return isNothing()?Maybe.of(f(this.val)):Maybe.of(null)
        }
        Maybe.prototype.isNothing=function(){
            return (!this.val == null)
        }

 

   錯誤處理、Either
    一、咱們的容器能作的事情太少了,try/catch/throw並非‘純’的。由於它從外部接管了咱們的函數,而且這個函數出錯時拋棄了它的返回值。
    Promise是能夠調用catch來集中處理錯誤
    事實上Either並不僅是來處理錯誤的,它表示了邏輯或,範疇學裏的coproduc
    - Either
    條件運算符if...else是最多見的運算之一,函數式編程裏面,使用Either函子表達。Either函子內部有兩個值:左值(left)和右值(right)。右值是正常狀況下使用的值,左值是右值不存在的時候使用的默認值。
    
        class Eitch extends Functor{
            constructor(left,right){
                this.left = left;
                this.right = right;
            }
            map(f){
                reutrn this.right?
                Eitch.of(this.left,f(this.right)):
                Eitch.of(f(this.left),this.right)
            }
        }
        Eitch.of = function(left,right){
            return new Eitch(left,right)
        }
        var addOne=function(x){
            return x+1
        }
        Eitch.of(5,6).map(addOne)//Eitch(5,7)
        Eitch.of(5,null).map(addOne)//Eitch(6,null)
        Eitch.of({address:"xxx"},currentUer.address).map(updateField)//代替try...catch

 

    ```//錯誤處理、Eitch
   
     var Left = function(x){
            this.__value = x
        }
        var Right = function(x){
            this.__value = x
        }
        Left.of=x=>(new Left(x));
        Right.of=x=>(new Right(x));
        Left.prototype.map=function(f){
            return this
        }
        Left.prototype.map=function(f){
            return this
        }
        Right.prototype.map=function(f){
            return Right.of(f(this.__value))
        }

 

        //left和right惟一區別就在與map方法的實現,Right.map的行爲和咱們以前提到的map函數同樣。可是left.map就很不一樣了:它不會對容器作任何事情,只是很簡單地把這個容器拿進來又扔出去。這個特性意味着,Left能夠用來傳遞一個錯誤信息.
        let getAge = user=>user.age?Right.of(user.age):Left.of("ERROR!")
            getAge({name:"zhangsan",age:12}).map(age=>`Age is ${age}`)//Right(Age is 12)
            getAge({name:"zhangsan"}).map(age=>`Age is ${age}`)//Left("Error")
    ```
        Left可讓調用鏈中任意一環的錯誤當即返回到調用鏈的尾部,這給咱們錯誤處理帶來了很大的方便,不再用一層又一層的try/catch
       AP因子
        一、函子裏面包含的值,徹底多是函數。咱們能夠想象這樣一種狀況,一個函子的值是數值,另外一個函子的值是函子。

            class AP extends Functor{
                ap(f){
                    return AP.of(this._val(f.val))
                }
            }
            AP.of(addTwo).ap(Functor.of(2))

 

 
        IO
        一、真正的程序老是要接觸骯髒的世界。
        function readlocalStorage(){
            return window.localStorage();
        }    
        二、IO跟前面那幾個Functor不一樣在於,它是__value是一個函數。它把不純的操做(好比IO、網絡請求、DOM)包裹到一個函數內,從而延遲這個操做的執行。因此咱們認爲,IO包含的是被包裹的操做的返回值。
        三、IO也算是惰性求值
        四、IO負責了調用鏈積累了不少不少不純的操做,帶來的複雜性和不可維護性
   
         import _ from 'lodash'
            var compose = _.flowRight;//函數由右至左執行
            var IO = function(f){
                this.__value = f;
            }
            IO.of=x=>new IO(x)
            IO.prototype.map=function(f){
                return new IO(compose(f,this__value))
            }
            //node
            var fs = require("fs");
            var readFile=function(filename){
                return new IO(function(){
                    return fs.readFileSync(filename,"utf-8")
                })
            }
            //flatMap() 返回映射結果
            readFile("../css3d/index.html").flatMap(tail).flatMap(print)
            //同等於

            //chain返回一個lodash實例,該實例包含value啓用顯示方法鏈序列的封裝
            readFile("../css3d/index.html").chain(tail).chain(print)    
相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息