函数式变成小思考3 笔记

JS函数式编程指南

第一遍阅读,
重点放在,提出遇到的不清楚的概念

1.介绍

疑问 什么是最小意外原则?
每个程序员都必须遵守的编程原则
最少意外原则通常是使用在用户界面设计上,但这个原则同样适用于编写程序。程序代码应尽可能的不要让阅读者感到意外。也就是说应该遵循编码规范和常见习惯,按照公认的习惯方式进行组织和命名,不符常规的编程动作应该尽可能的避免。

海鸥例子,作者狠狠给我打了一个脸,
刚开始用的是面向对象的思维,写的一段程序

var Flock = function(n) {
  this.seagulls = n;
};

Flock.prototype.conjoin = function(other) {
  this.seagulls += other.seagulls;
  return this;
};

Flock.prototype.breed = function(other) {
  this.seagulls = this.seagulls * other.seagulls;
  return this;
};

var flock_a = new Flock(4);
var flock_b = new Flock(2);
var flock_c = new Flock(0);

var result = flock_a.conjoin(flock_c).breed(flock_b).conjoin(flock_a.breed(flock_b)).seagulls;

后来用函数简化成了
加法和乘法的问题

var add = function(x, y) { return x + y };
var multiply = function(x, y) { return x * y };

var flock_a = 4;
var flock_b = 2;
var flock_c = 0;

var result = add(multiply(flock_b, add(flock_a, flock_c)), multiply(flock_a, flock_b));

这里得到的启示是,
面向对象的方式,比较容易描述我们看到的情况和故事,
比较容易翻译我们的思维.

而面向函数进行简化后,得到的启示是,
它有可能会首先进行数学上的关系分析, 然后进行数学上的抽象,
抽象出一般关系之后, 再用代码表示出来.

下面这个例子又打了我一脸

var hi = function(name){
  return "Hi " + name;
};

var greeting = function(name) {
  return hi(name);
};

简化

var greeting = hi;


greeting("times");
/ "Hi times"

这两天我还在想, 函数的多层嵌套, 有种种神奇的效果,特别是,参数,功能,执行相分离.
但这里直接揭示出, 有些函数嵌套,就是脱了裤子放屁,
增加代码阅读难度.

作者似乎对this非常痛恨
或者说函数式编程对于,继承这种行为很不感冒?

纯函数的好处

  • 依赖状态是影响系统复杂度的罪魁祸首

Object.freeze 方法是什么? Object.freeze()

什么是作用?
作用 我们可以理解为一切除结果计算之外发生的事情。
什么是副作用?
副作用是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互。
只要是跟函数外部环境发生的交互就都是副作用

从定义上来说,纯函数必须要能够根据相同的输入返回相同的输出;如果函数需要跟外部事物打交道,那么就无法保证这一点了。

戏剧性的是:纯函数就是数学上的函数,而且是函数式编程的全部。

似乎可以让一个非纯函数, 变成一个纯函数

追求"纯" 的理由

  • 可缓存性(Cacheable)
  • 可移植性/自文档化(Portable / Self-Documenting)
    (依赖明确)
  • 可测试性(Testable)(不用配置环境,只需要好好传参)
  • 合理性(Reasonable)
  • 并行代码

什么是引用透明性?
如果一段代码可以替换成它执行所得的结果,而且是在不改变整个程序行为的前提下替换的,那么我们就说这段代码是引用透明的。
所以纯函数 是引用透明的.

什么是依赖注入?

  • 什么是依赖注入
    用的是spring来解说的, 没学过这个.
    好像是说,有调用者, 和被调用者.
    依赖注入的使用的结果就是, 让调用者和被调用者,只在运行时有依赖关系?
    两者的定义过程互不相干?
    但运行时想要有所相干(有依赖), 则必须让两者留出接口,互相满足这个接口的规则?
    或者这个接口可以都跟第三方也就是spring来接洽?
  • 什么是依赖注入?
    将程序创建的过程交给框架进行处理,管理程序对声明周期和模型,自动注入到需要使用到另一个程序中的过程,成为依赖注入。
    我们可以看到一个最大到特点,依赖注入这种程序到编写和理解方式对我们对代码初期造成了极大对复杂度,但是如果程序越来越大,特别是呈几何倍对增长时,依赖注入管理程序带来对好处与所损耗对性能来说是不值一提对。

首先这是用 模拟angular的一些代码来解说的, 我还没学过angular.
其次 尽管我没学过angular 但我觉得他模拟的不是很好, 有些参数没有用上(比如name)
然后就是, 排版上有些乱码, 还有错别字.
最后就是,我有点理解他说的依赖注入是什么, 但不是很清晰.


三、依赖注入是什么?
这是用C#写的, 也没学过, 虽然也没有完全理解, 但讲得似乎非常友好.
似乎是这样的,(我这都是似乎,哎..), 几个函数(对象)之间有依赖关系,
但我们希望这几个函数之间不是"强依赖"?


javascript依赖注入详解
依赖倒转原则(Dependence Inversion Priciple, DIP)提倡:
. 高层模块不应该依赖低层模块。两个都应该依赖抽象
. 抽象不应该依赖细节,细节应该依赖抽象
. 针对接口编程,不要针对实现编程

其实依赖注入它只做两件事:
. 初始化被依赖的模块
. 注入到依赖模块中
感觉差一层纸, 我就明白了.
前端需要知道的 依赖注入(Dependency Injection, DI)
细数Javascript技术栈中的四种依赖注入
依赖注入那些事儿
先mark 依赖注入问题, 单独好好学习一下.

我们可以使用一种叫做“等式推导”(equational reasoning)的技术来分析代码。
什么是等式推导技术?
主要是应用 结合律 和 交换律 来重构代码?

看了一遍
第四章, 柯里化,
第五章, 函数组合,
第六章, 示例应用,
特别是看完示例应用之后,
突然就感觉到这个柯里化和函数组合,加起来用处很大.

有多个参数的函数, 可以进行柯里化, 将参数分离,
返回新的函数, 可以控制参数让返回的函数只有一个参数,便于组合.
将函数柯里化之前, 我们可以用一个函数进行包裹, 用来控制参数的位置.
让我们需要的参数放在最后一位.

函数的组合也会返回一个新的函数.
执行顺序为从右向左,将前一个函数执行返回的数据传给下一个.
这里的关键是, 进行组合的函数, 似乎只能接收一个参数,
这也是为什么函数柯里化重要的地方,
另一个关键是, 函数的组合满足交换律.
这也是后来在示例应用中, 进行的代码重构令我大吃一惊的原因.

示例应用中的练习, 给我们的启示是,
我们可以应用 函数组合和函数柯里化,
让那些有依赖关系的普通函数, 都应用 函数组合和函数柯里化.
然后进行重构.
这跟我之前的印象是大不相同的,

我之前以为, 函数组合, 柯里化的应用方式是, 在比较限定的情况下,
可以用到.
而这里明明就是, 任何有函数依赖的代码,都可以用这个来优化.

还有一个概念是比较陌生的.
范畴论是什么?


第七章 Hindley-Milner 类型签名
起初很亲切, 很像 学过的es6语法.
确定输入的类型,和输出的类型.用->连接.
相当的简化形式, 但信息量是丰富的. 而且还是比较直观的.
而这种形式上的超级简化, 让我联想起 数学.
想要弄数学, 也是要把一些关系, 简化成 一些符号.
运用起来才比较顺手, 才流畅.
用了类型签名之后, 就更像贴近数学的感觉了.

不过跟es6语法中的表示也很不同,
es6中的参数只代表值, 不提供参数类型的信息.(从变量名中可能有所猜测)
而类型签名中, 最重要的内容就是要提供类型信息.
es6中,如果存在多个参数 可以表示成这样
(a,b) = > { a + b}
而作者的类型签名当中, 所有的函数都看成是柯里化的函数
a -> b -> c

然后就是,突然间的高潮,

自由定理(free theorems)

/ head :: [a] -> a
compose(f, head) == compose(head, map(f));

/ filter :: (a -> Bool) -> [a] -> [a]
compose(map(f), filter(compose(p, f))) == compose(filter(p), map(f));

作者说这是常识... 我怎么没感觉是常识?
先不进行理解,
假设,上面的左右表达式都是相等的, 也就是可替换的.
那compose 交换位置实际上效果是很不一样的.
通过compose 交换 也许能够大幅度的简化代码?

之前的定理是

compose(a,b,c,d,e) == compose(a,b,compose(c,d),e)

这个定理是可以方便的任意结合函数,
但有个前提条件是, 顺序问题, 严格的从右向左的顺序.

可是自由定理玩的是顺序上的打乱.
自由定理的应用是否存在一些使用条件?
这肯定是存在限定条件的, 因为执行顺序太重要了
比如

function a (str) {
    return str + 'abc'
}
function b (str) {
    return str.toUpperCase()
}

let c = a(b('string'));
console.log(c);/   STRINGabc
let d = b(a('string'));
console.log(d);/STRINGABC

上面的自由定理能够使用, 我觉得应该跟 map函数有关系.
或者像map 和 filter一样, 把函数当做参数的函数, 可能符合自由定理?
有待思考.

不过, 根据作者的意思是,
如果可以, 我们应该尽量把所有函数都用 类型签名 标注一下.
既可以当做注释, 又可以理清思路, 能够看到自己到底干了什么
不过类型签名似乎只用在定义函数的时候,

后面的几章,真的是匆匆看了一遍,
总体印象是, 跟前面的内容很不相同, 应该算是第二部分的内容.
主要讲的是函子, functor, 理论支撑是 范畴论
然后就是 各种函子 和 前面讲到的 柯里化的函数之间的 交织应用.
如果说上半部分的代码中的基本单位是 各种函数的话,
那么下半部分的代码中的基本单位就是 各种函子.
或者说, 前面讲的各种纯函数, 都被包裹进了函子里.
而这个函子, 从形式上来讲, 是个对象.
最让我感觉牛逼的地方在于, 就是突然的某一瞬间,
他用对象实现了一堆东西之后, 开始应用范畴论的各种定律.
说实话, 之前敲代码的时候, 感觉就像是写故事,
虽然存在优化, 但没有像看这本书时的感觉,
强烈的数学色彩, 公式, 定律, 推导, 替换, 真的是神乎其神.
当然, 完全有可能是因为我什么都不懂,
我相信, 什么时候, 我感觉这个东西, 一点都不神奇的时候,
可能就是我入门的时候.

我决定后面这几章, 重新看一遍,
之前看一遍时, 很多东西需要通过代码理解,
可问题在于, 很多代码要么是前面的章节, 我没记住, 总是懒得往前去看
要么是有些库,我没有引进来.
所以到后来, 代码很难看懂.(我相信作者写的已经是仁至义尽了)
最主要的原因就是, 我没有跟着敲.
所以第二遍, 我决定把大部分的代码都敲一遍,
以便文中提及时, 不至于不记得,或者找的慢.

然后就是, 后面这几章
跟阮一峰-函数式编程入门教程
重合度比较高,
或者说, 阮一峰老师写的风格更合我心,
实际上js函数式指南的作者,废话有点多...哈哈哈哈哈..可能咱们不是一个国家..
不过看了网上讲的好多函数式,大多数的文章的源头,
都是这个.
所以我们还是把这个先看完, 先试着消化一下.

第二遍, 我们把代码抄录一下.



    
        
        

    
    
        
        





    


/ 容器,存放任何类型的数据, 函子/ 这种最基本的container 也称为 Identity
            var Container = function(x) {
                this.__value = x;
            }
            / 构造器
            Container.of = function(x) {
                return new Container(x);
            }
            / of 返回一个容器
            / map : map 做了什么? 把容器中的值 通过 f 处理之后, 把返回值放进了另一个容器中
            Container.prototype.map = function(f) {
                return Container.of(f(this.__value));
            }
            / map 也返回一个容器/ 保证了能够链式调用!



            / 能够在map运行前, 检查是否为空值.
            var Maybe = function(x) {
                this.__value = x;
            }
            / 构造器
            Maybe.of = function(x) {
                return new Maybe(x);
            }
            / of 返回一个容器
            / map : map 做了什么? 把容器中的值 通过 f 处理之后, 把返回值放进了另一个容器中
            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));
            }
            
            /  map :: Functor f => (a -> b) -> f a -> f b 
            var map = curry(function(f, container) {   return container.map(f); });
            / map 返回的是一个容器
            / 此处的map 和 上面的map 就不一样了, 上面的map 是用来处理数组的
            / 这里的map 是专门用来处理 容器的
            
            / 改成这样之后, map就变成了纯函数, 可以参与 compose了.
            / 哇, 好聪明啊
            
            / 稍微观察就会发现, 我们的 f 想要跟 value 接头, 就必须要通过 map,
            / 到目前为止  map 是唯一的接头通道.
            
            
            / 有点类似 之前学compose 时的 trace
            var maybe = curry(function(x, f, m) {   return m.isNothing() ? x : f(m.__value); });
            / 我们发现, compose接收的参数,确实都是纯函数,
            / 但有一个特别的地方, 如果上一个函数返回值是个 容器, 则下一个函数则最好是包裹在 map里.
            / 咦? 其实这个 map 和 数组curry的map 非常类似.
            
            
            / 例子
            /  withdraw :: Number -> Account -> Maybe(Account)
var withdraw = curry(function(amount, account) {
  return account.balance >= amount ?
    Maybe.of({balance: account.balance - amount}) :
    Maybe.of(null);
});

/  finishTransaction :: Account -> String
var finishTransaction = compose(remainingBalance, updateLedger); / <- 假定这两个函数已经在别处定义好了

/  getTwenty :: Account -> Maybe(String)
var getTwenty = compose(map(finishTransaction), withdraw(20));


getTwenty({ balance: 200.00});
/ Maybe("Your balance is $180.00")

getTwenty({ balance: 10.00});
/ Maybe(null)

/ 加了maybe 之后
/  maybe :: b -> (a -> b) -> Maybe a -> b
var maybe = curry(function(x, f, m) {
  return m.isNothing() ? x : f(m.__value);
});

/  getTwenty :: Account -> String
var getTwenty = compose(
  maybe("You're broke!", finishTransaction), withdraw(20)
);


getTwenty({ balance: 200.00});
/ "Your balance is $180.00"

getTwenty({ balance: 10.00});
/ "You're broke!"

/ Either 
/ Either 和 Maybe的区别
/ Maybe 是对value做了空值检查后, 如果是空值, 则返回的容器里的值为null, 没有其他信息
/ Either 是 设定两个 容器 left,right, 对value 做了判断之后, 会决定返回哪个容器, 返回left 时 会返回设定的信息?
/ 两者都能让程序在数据不对的时候, 停止后续的操作(Maybe 想要让后续动作停止, 只能返回null)
/ Either 可以在停止后续程序时, 还能返回错误信息

/ lift
/通俗点来讲,一个函数在调用的时候,如果被 map 包裹了,那么它就会从一个非 functor 函数转换为一个 functor 函数。我们把这个过程叫做 lift。

var either = curry(function (f, g, e) {
    switch (e.constructor){
        case Left : return f(e.__value);
            break;
        case Right: return g(e.__value);
            break;
    }
})

/ 可以看到, 不只是通过 map , 也可以通过either 让函数接触 容器里的数据, 只是返回的不是容器



/ 把非纯函数,变成纯函数
var getFromStorage = 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.of = function(x) {
/  return new IO(x);
/}
/ 值确实存在于容器中, 但却是通过函数
IO.prototype.map = function(f) {
  return new IO(compose(f, this.__value));
}
/ 对比
/IO.prototype.map = function(f) {
/  return new IO(f(this.__value));
/}
/ 这个返回的也是容器, 只是容器内存的却是个函数.
/ 所以存的是个函数, 这就是IO的特点?
/ var value = x 和 var value = functon (x) {return x} 的区别从概念上要怎么讲?
/ 之前了解到, function 的定义本身有个作用就是延迟执行, 可以跨越时间
/ 当时只是针对功能而言, 如果按照上面的情况来讲,
/ 则相当于 延迟求值?
/ 但function (x) {return x} 返回值其实是不确定的, 要看执行时传什么,
/ 但function (x) {return x} 这个函数本身是确定的.
/ 所以能够把不纯的函数变成纯函数?
/ 回顾一下, 什么是纯函数? 相同的输入, 确保相同的输出, 不受到环境变量影响, 也不会影响环境变量.
/ 上面这个能做到这个要求嘛?
/ 上面这个似乎是能做到, 但那些有副作用的函数呢?
/ 假设是这样呢?
/ function () {return x}/ 此时 x 就是环境变量. 这应该不能算是纯函数.
/ 但, 这个函数应该算是一个确定的值? 只要这个函数没有运行,就不会有副作用?
/ value代表的函数, 我们不看成一个函数, 而是看成一个值.


/ 下面这一段开始, 突然间就变得很难了

/  url :: IO String
var url = new IO(function() { return window.location.href; });

/  toPairs =  String -> [[String]]
var toPairs = compose(map(split('=')), split('&'));

/  params :: String -> [[String]]
var params = compose(toPairs, last, split('?'));

/  findParam :: String -> IO Maybe [String]
var findParam = function(key) {
  return map(compose(Maybe.of, filter(compose(eq(key), head)), params), url);
};

/// 非纯调用代码: main.js ////

/ 调用 __value() 来运行它!
/ 前面所有的操作,包括compose, map 等等,
/ 它们的输入值 和 返回值,全都是函数
/ 只有最后,.__value() 才能执行
findParam("searchTerm").__value();
/ Maybe(['searchTerm', 'wafflehouse'])

/ 都是把对未来的操作的指示放在一个时间胶囊里!


/ 同一律
let id = function (x) {return x}
let map1 = curry(function (f,arr) {
    return arr.map(f);
})
let map2 = curry(function (f,container) {
    return Container.of(f(container.__value));
})
var id1 = map1(id) / 返回的是一个函数
var id2 = map2(id) / 返回的也是一个函数
/ 没错 无论是id ,id1,id2 哪个函数接收一个容器时, 都会返回同样的容器.
/ 当然 id1 应该接收的是 数组, id2接收的是个容器, 而id 则能接收任何类型

/同一律
map(id) === id;/ 这两个函数的效果是完全相同的, 但实际上是两个引用值,所以不可能绝对相等.

/组合律
compose(map(f), map(g)) === map(compose(f, g));
/ map(f) 就是个函数. 用来 接收一个 容器(或数组), 将值进行处理过后, 返回一个存了该值的容器.
/ 实际上, map 只是用来打开 容器, 以及返回容器的作用,
/ 真正表示值 和 功能的 是 容器的 value 和 传进来的函数 f,g
/ 所以上面这个组合律, 其实是可以理解的.
/ 只不过要理解map 是上面的这个意思.
/ 但对于map的这层理解, 实际上我第一次读这段的时候, 是比较难理解的.
/ 因为,我潜意识, 下意识的学习反应是, 想要还原这段代码.
/ 也就是, 把 compose 的源码, map 的源码都还原,
/ 最好把f和g两个函数也都用具体的函数来替代,
/ 把整个代码,按照左右全都具体还原出来, 走一遍,
/ 然后我从中理解.
/ 我一直以为我这种学习方式是对的.因为可以从'底层'了解,
/ 但现在觉得,其实这种学习方式是很有问题的, 
/ 是一种所谓的"不怕艰难"却实际上是 "傻"的方法,
/ 因为这种还原本身就需要大量工作, 而且这其实可能更不好理解.
/ 因为抽象本身, 有时就是为了容易思考, 而进行的简化.
/ 非要把他还原成具体的例子, 具体的代码, 就好像是自找苦吃.(这么想来, 我确实杀了自己不少的脑细胞)
/ 不是说,这种方法一无是处, 肯定也是有好处的, 因为真的走一遍走通的话,
/ 在这个过程当中, 总会体验更多的细节, 更深的印象.
/ 这就像是, 学一个 乘法公式, 我非要 把7 * 8 模拟出来,
/ 可能是用七个盒子, 每个盒子8个糖的方式, 又或者 用图画七行八列的圆圈,
/ 然后一个一个数完, 确定是56时, 我才觉得自己理解了.

/ 这种方法确实很笨, 不太可取, 因为效率太低了.
/ 但从个人角度来讲, 我不讨厌这种方式, 
/ 不过以我现在的处境, 把所有的东西都按照这种方式全模拟一遍, 是否太奢侈了?

/ 不过, 如果我只能通过这种方式,才能获得比较透彻的理解, 那我别我选择, 只能选择这种方式.
/ 有时, 确实要承认自己比较笨.



/下面这段代码又看不懂了, 而且, 作者破天荒的, 没写类型签名..尼玛


/ 交换律
compose(Maybe.of, reverse) === compose(map(reverse), Maybe.of);
/ 跟上面的组合律不同的是, 交换律 是 影响了顺序.
/ 从另一个角度来讲, 就是, f , F 这两种 函数, 在顺序上是可以互换的,
/ 不会影响最后的结果., 当然 f 和 f 之间, 顺序上就会有影响.
/ 原因在于, f 是能够对值产生影响, 会生成另一个值
/ 而 F的作用只是进行一次包裹.包裹之后, 值是不会变的, 只是换了个存储位置.
/ 包裹之间 用f 操作 返回另一个值, 包过之后, 用map(f), 来操作返回另一个值, 只是被包裹在一个容器中.

/ 用范畴学, 简单重新想一遍



    
        
        函数式编程定律
    
    
        

    


你可能感兴趣的:(函数式变成小思考3 笔记)