函數(shù)式編程小思考4 筆記

JS函數(shù)式編程指南(https:/llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/ch2.html)

現(xiàn)在開始第三遍的閱讀

說實話, 我只是在思考函數(shù)的時候, 遇到一些疑問,
在我試著整理這些疑問的時, 打開了關(guān)于函數(shù)式編程相關(guān)的東西,

我沒有想到會消耗這么長時間,(以為就是讀一兩篇博客的概念)
或者說, 耗費的時間, 遠遠超過了,當初的時間預算.
當然其中最大的原因應(yīng)該在于我的低效率學習方式.

到昨天為止, 我已經(jīng)讀了兩遍,
能夠把文中的大代碼全都讀明白了(就是能夠讀懂,并非是熟悉,或者深入掌握)
猶豫半天, 我是先放在這里, 還是說, 要繼續(xù)掌握一下.
很明顯, 如果在這里停止, 我對函數(shù)的理解確實應(yīng)該潛移默化的增加了理解,
當從另一角度來講, 不亞于淺嘗即止.
而網(wǎng)上有一些說法是,函數(shù)式編程在實際的工作當中, 可能也不會用多少.
更多的應(yīng)該是面向?qū)ο蟮木幊?

其實這幾天讀這個函數(shù)式編程指南, 很費勁, 特別是到了最后3章,
代碼突然就有點看不懂.
我想還是試著去掌握一下, 不過, 這回, 我們不要把全部時間都投入在這里,
我們把 這個當成一個 長期的任務(wù), 每天抽出一些時間回顧一遍的形式, 可能更好一點.

而且說實話, 這個編程指南提供的是一種,工具, 嗯, 是一種全套解決代碼的工具,
有點理解為什么叫編程范式了.
不過實際上, 他沒有完全解決我最初的疑問.
我最根本的疑問是, 我始終感覺, 無法完全掌握函數(shù)的用法.
形式很簡單, 但變化太多樣, 效果也太多樣.
具體的表現(xiàn)的問題是, 多層函數(shù)的效果, 與用法.

但回頭回答這個問題之前, 我們先讀第三遍, 函數(shù)式編程指南.

問題一, 作者是怎么能夠快速看出 等價的?

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

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

            var greeting1 = hi;

            hi = function(name, age) {
                return "Hi " + name + 'age is ' + age;
            };

            / 太傻了
            var getServerStuff = function(callback) {
                return ajaxCall(function(json) {
                    return callback(json);
                });
            };

            / 這才像樣
            var getServerStuff = ajaxCall;
            
            
            / 因為, 功能, 輸入的參數(shù), 和返回的數(shù)據(jù), 都嚴格一致
            / 所以首先要看到, 功能是什么, 進入的參數(shù)類型,路徑是什么
            / 返回的數(shù)據(jù)類型, 路徑是什么
            
            
            / 這里有值得思考的問題
            / 確實函數(shù)多層嵌套, 會消耗人更多的腦力, 看不太懂.
            / 或者我似乎好像認為, 多層函數(shù)嵌套本身, 就一定是難的.
            / 或者說, 我似乎, 從沒有進行過一種訓練,
            / 這種訓練應(yīng)該是, 如何能識別一個多層函數(shù)結(jié)構(gòu)
            / 第一種比,第二種難的第一個地方是, 變量更多,
            / 總共有 4個變量, getServerStuff,callback,ajaxCall,json
            / 并且總共出現(xiàn)6次
            / 我需要記住4個變量, 并且要找到參數(shù)的來路, 返回值的去處, 
            / 還要思考,執(zhí)行順序.
            
            / 可作者是怎么看出來的? 怎么快速看出來的?
            / 這里肯定存在一種方法.
            
            / 好吧, 這里是我們第一個遺留的問題.
            / 也許, 作者很熟悉結(jié)構(gòu)的'語義'

/ 換句話講, 作者能夠比我能夠更快的看出, 一個函數(shù)到底干了什么?
/ 所以語義化理解代碼, 是第一步?

功能和函數(shù)的區(qū)別在這里

函數(shù)只是兩種數(shù)值之間的關(guān)系:輸入和輸出。


可以通過延遲執(zhí)行的方式把不純的函數(shù)轉(zhuǎn)換為純函數(shù):

var pureHttpCall = memoize(function(url, params){
  return function() { return $.getJSON(url, params); }
});

所謂的延遲執(zhí)行,就是返回一個函數(shù).
也就是, 一個函數(shù)的執(zhí)行返回的值, 弄成函數(shù),
函數(shù)充當這個'值',
這里的意義在于, 他把一個執(zhí)行, 弄成了一個值, 一個變量.


純函數(shù)的優(yōu)點之一, 是依賴關(guān)系透明,

不依賴環(huán)境變量, 不改變環(huán)境變量的意思是,
所有函數(shù)內(nèi)部需要的外部變量,
都必須通過,參數(shù)入口傳進來.(這算是依賴注入?)
這樣有兩個衍生的好處,
1,可移植性, 因為, 接口是確定的,就是參數(shù)入口, 我們想要更改的時候,
只需要在把數(shù)據(jù)按照要求的格式改一下, 依然放在那個入口參數(shù)的位置即可
我們能很清楚的看到發(fā)生了什么.
如果不是通過入口函數(shù)而來, 而是來自環(huán)境變量, 那么這個替換過程, 就很不透明,
回過頭來的時候, 已經(jīng)不知道這個數(shù)據(jù)從哪里來了.
2, 可測試性, 因為對環(huán)境變量沒有依賴, 只對入口參數(shù)有依賴,
所以,我們只需要把需要的參數(shù)好好傳進去就可以進行測試,
而不需要費力把整個運行環(huán)境都大概模擬出來

curry 柯里化

            var memoize = function(f) {
                var cache = {};

                return function() {
                    var arg_str = JSON.stringify(arguments);
                    cache[arg_str] = cache[arg_str] || f.apply(f, arguments);
                    return cache[arg_str];
                };
            };

            function sub_curry(fn /*, variable number of args */ ) {
                var args = [].slice.call(arguments, 1);
                return function() {
                    return fn.apply(this, args.concat(toArray(arguments)));
                };
            }

            function curry1(fn, length) {
                // capture fn's # of parameters
                length = length || fn.length;
                return function() {
                    if(arguments.length < length) {
                        // not all arguments have been specified. Curry once more.
                        var combined = [fn].concat(toArray(arguments));
                        return length - arguments.length > 0 ?
                            curry(sub_curry.apply(this, combined), length - arguments.length) :
                            sub_curry.call(this, combined);
                    } else {
                        // all arguments have been specified, actually call function
                        return fn.apply(this, arguments);
                    }
                };
            }
            // 應(yīng)用lodash里的curry
            let curry = _.curry;

柯里化干了什么?
用我自己的理解來講就是
他的第一層意思是, 功能延遲執(zhí)行
第二層意思是, 參數(shù)相分離, 在時間上, 空間上相分離?

只傳給函數(shù)一部分參數(shù)通常也叫做局部調(diào)用(partial application), 展示了一種預加載的能力

幾個curry化函數(shù), 后面的練習題及示例都用得到.

            var match = curry(function(what, str) {
                return str.match(what);
            });

            var replace = curry(function(what, replacement, str) {
                return str.replace(what, replacement);
            });

            var filter = curry(function(f, ary) {
                return ary.filter(f);
            });

            var map = curry(function(f, ary) {
                return ary.map(f);
            });
            var split = curry(function(what, str) {
                return split(what, str)
            })
            var reduce = curry(function(f, init, arr) {
                return arr.reduce(f, init)
            })
            var slice = curry(function(start, end, arr) {
                return arr.slice(start, end);
            })

            var toLowerCase = function(str) {
                return str.toLowerCase();
            }

            var join = curry(function(what, arr) {
                return arr.join(what)
            })
            
            var concat = curry(function (what,str) {
                return str.concat(what)
            })

            var id = function(x) {
                return x;
            };
            var trace = _.curry(function(tag, x) {
                console.log(tag, x);
                return x;
            });

上面的過程, 我們完成了一種轉(zhuǎn)換.
比如 arr.slice(start,end), 這里有功能, 有 參數(shù),
通過包裹一層函數(shù), 并柯里化, 讓這些功能和參數(shù),分割了開來.
進行了所謂的預加載,
并且, 可以根據(jù)需要隨意調(diào)整參數(shù)的順序,
這個順序就是, 我獲取數(shù)據(jù)的順序.

練習題

// 練習 1
            //==============
            // 通過局部調(diào)用(partial apply)移除所有參數(shù)

            var words = function(str) {
                return split(' ', str);
            };

            // 沒聽懂什么意思,
            // 局部調(diào)用, 應(yīng)該是讓我用 curry
            // 移除所有參數(shù), 這里參數(shù)只有 str 和 ' ', 移除參數(shù)是什么意思?
            // 是像下面這樣嘛? // 但這樣有什么意義? 只有一個參數(shù)時, curry的意義是什么?
            var words = curry(function(str) {
                return split(' ', str);
            })
            // 或者是這樣?
            var split = curry(function(what, str) {
                return split(what, str)
            })
            var words = split(' ');

            // 練習 1a
            //==============
            // 使用 `map` 創(chuàng)建一個新的 `words` 函數(shù),使之能夠操作字符串數(shù)組

            var sentences = undefined;

            var sentences = function(strs) {
                return map(words, strs);
            }
            // 這樣用, 似乎不太好,

            // 練習 2
            //==============
            // 通過局部調(diào)用(partial apply)移除所有參數(shù)

            var filterQs = function(xs) {
                return filter(function(x) {
                    return match(/q/i, x);
                }, xs);
            };

            var filterQs = filter(match(/q/i));
            // 這里的filter,match 都是curry過的函數(shù), 返回值為 函數(shù).
            // 這個過程卻是挺神奇的, 因為我們確實消除了所有參數(shù)! 沒有設(shè)置形參!

            // 練習 3
            //==============
            // 使用幫助函數(shù) `_keepHighest` 重構(gòu) `max` 使之成為 curry 函數(shù)

            // 無須改動:
            var _keepHighest = function(x, y) {
                return x >= y ? x : y;
            };

            // 重構(gòu)這段代碼:
            var max = function(xs) {
                return reduce(function(acc, x) {
                    return _keepHighest(acc, x);
                }, -Infinity, xs);
            };
            // 解答

            var max = reduce(_keepHighest, -Infinity);
            // 確實很神奇,, 用了curry函數(shù) 之后, 我們確實可以消除形參, 代碼看起來確實很簡潔
            // 當然前提是, 要了解這些curry函數(shù), 起碼要知道需要幾個參數(shù), 都代表什么含義.

            // 彩蛋 1:
            // ============
            // 包裹數(shù)組的 `slice` 函數(shù)使之成為 curry 函數(shù)
            // //[1,2,3].slice(0, 2)
            var slice = undefined;

            var slice = curry(function(start, end, arr) {
                return arr.slice(start, end);
            })

            // 彩蛋 2:
            // ============
            // 借助 `slice` 定義一個 `take` curry 函數(shù),該函數(shù)調(diào)用后可以取出字符串的前 n 個字符。
            var take = undefined;
            
            var take = slice(0);

上面的練習題, 主要練習的就是, 通過一些已經(jīng)進行過柯里化的函數(shù),
進行參數(shù)調(diào)用的方式,
消除了形參.
消除形參的方式, 叫什么point free?
很有用處, 一來可以簡化代碼, 沒有太多 function() {} 嵌套的格式
二來, 根據(jù)前面的章節(jié)所說, 如果設(shè)置形參, 則后續(xù)要更改的時候, 這些包裹的中間層函數(shù)都要更改.


pointfree

pointfree 模式指的是,永遠不必說出你的數(shù)據(jù)
我的理解是, 永遠不用設(shè)置你的形參,( 函數(shù)存在于變量名, 函數(shù)存在于返回值.)

一等公民的函數(shù)

指的應(yīng)該是, 用變量的方式 表示函數(shù)的意思?

利用 curry,我們能夠做到讓每個函數(shù)都先接收數(shù)據(jù),

看下面這個例子

// 非 pointfree,因為提到了數(shù)據(jù):word
var snakeCase = function (word) {
  return word.toLowerCase().replace(/\s+/ig, '_');
};

// pointfree
var snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);

看到組合和柯里配合的驚人之處了嘛?
首先, 無論是 curry 還是組合, 做到的第一件事情都是延遲執(zhí)行.參數(shù)待定.
在這里, 因為replace 是個柯里化的函數(shù), 所以可以先進行一次數(shù)據(jù)的加載,
等到sankeCase 執(zhí)行的時候, 在獲取最后一個數(shù)據(jù). 這在時間上, 數(shù)據(jù)的加載是 分開的.
不止如此,
compose的意義在于, 規(guī)定了函數(shù)執(zhí)行的順序?
又或者是, 最后一個數(shù)據(jù)的來源?
突然發(fā)現(xiàn), compose的缺陷就是, 傳輸數(shù)據(jù)的通道只能有一個, 也就是每個函數(shù)的最后一個參數(shù).
也就是說, 在上compose這趟列車的所有函數(shù), 都必須要把除了最后一個參數(shù)之外的其他參數(shù)都綁定掉.
比如,一個柯里化的函數(shù), 可以分隔三個參數(shù),
但為了上compose這趟列車, 他必須要把除了最后一個參數(shù)之外的其他參數(shù)都要綁定掉.
但這個問題似乎也能解決.

我們稍微改一下
            var snakeCase = function (what,when,where) {
                return compose(replace(what, '_'), toLowerCase(when),areyouok(where));
            }
假設(shè)上面的是哪個函數(shù), replace, toLowerCase,areyouok 三個都是柯里化函數(shù)
每個函數(shù)都有需要待定的參數(shù).
那我們就可以給這個compose 包裹上一個函數(shù), 使其可以變成待定.
也就是說包裹一層函數(shù)最大的用處之一,就是延遲執(zhí)行, 并讓參數(shù)可以變得待定.
配合curry, (也就是多層函數(shù)嵌套,)則可以完成參數(shù)綁定時機的分離.
            var snakeCase = curry(function (what,when,where) {
                return compose(replace(what, '_'), toLowerCase(when),areyouok(where));
            })

觀察一下就會發(fā)現(xiàn), 通過每次執(zhí)行狀態(tài)(compose()的執(zhí)行也是一種執(zhí)行),時,
我們都可以通過這種方式,使得延遲執(zhí)行, 以及參數(shù)分離.
或者反過來講,
如果我們想要通過函數(shù)定義包裹的方式實現(xiàn)參數(shù)分離, 延遲執(zhí)行,
就必須要把執(zhí)行形態(tài)包裹進去, 或者說,必須存在執(zhí)行形態(tài),
這樣我們才能匹配?
(雖然我自己都不知道自己在說什么, 但我覺得還挺重要的)

而且實際上上面是存在兩次延遲, compose執(zhí)行返回的函數(shù),本身也是有延遲效果的.
反過來講, 我們可以通過上面的方式,
始終可以把先確定的功能和函數(shù)綁定, 把為確定的功能和數(shù)據(jù),進行延遲.

突然發(fā)現(xiàn), 上面的柯里化函數(shù), 也是有缺陷的, 這個缺陷在于, 參數(shù)的順序問題.
即, 柯里化函數(shù)的時候, 形參的順序, 變得非常重要,
從某種角度來講, 我們定義形參順序的時候, 必須要知道, 數(shù)據(jù)綁定的順序.
需要依次進行綁定, 不能亂了順序.
但這是有問題的.
因為完全存在一種需求是, 我們不知道會先出現(xiàn)綁定哪個數(shù)據(jù).

我用例子來講一下上面的需求

            var add = curry(function (x,y,z) {
                return x * x + y * 2 + z - 1
            })
            add(y)(z)(x);
在這個例子中, 傳參順序的規(guī)定, 使得不能達到我的預期.

我想了半天,確實很難解決這個問題, 簡單來講, 
我傳入的值, 需要讓柯里化的函數(shù)能夠識別哪個值對應(yīng)是哪個參數(shù)
像上面的情況, 默認識別方式就是,根據(jù)傳參的順序.
假設(shè)我們想要通過別的方式識別, 就必須要有一個額外的信息來標記自己是誰,

比如說,
add({value:y,index : 1})({value:z,index : 2})({value:x,index : 0})
當然這要對封裝的curry函數(shù)進行改動。
暫且不說如何改動,光是這種調(diào)用形式, 是否稍顯臃腫,且不便。
應(yīng)該是非常不方便的,
或者, 我們傳值時, 不能攜帶多余信息, 但調(diào)用時,是否可以添加額外信息?
比如這樣調(diào)用
add.index(1)(y).index(2)(z).index(0)(x)
也就是, curry返回的函數(shù)中弄一個index接口,
不調(diào)用這個接口時, 按正??吕锘瘮?shù), 順序的方式進行,
如果調(diào)用了這個接口, 則按照這個順序,進行柯里化?

感覺不是不可以啊...感覺有戲啊.
明天可以試著寫一下?

突然又想到一個問題,

假設(shè)已知 add函數(shù)和curry,能夠得到 curry(add)
            var add = function (x,y,z) {
                return x * x + y * 2 + z - 1
            }

如果已知curry(add) , 能否逆推得出add 函數(shù)?

這兩個問題,都要求深入了解curry, 明天再干吧,,或者先略過去

trace 用來和 compose 配合, 查看錯誤在哪里, 很有用, 如果用compose, 就最好知道trace

var trace = curry(function(tag, x){
  console.log(tag, x);
  return x;
});

var dasherize = compose(join('-'), toLower, split(' '), replace(/\s{2,}/ig, ' '));

dasherize('The world is a vampire');
// TypeError: Cannot read property 'apply' of undefined

組合的另一個優(yōu)點,(特別是相比柯里化),
組合能夠大大增強,代碼的可讀性, 因為語義化非常的好.
所以比較容易閱讀.

練習題

require('../../support');
            var _ = require('ramda');
            var accounting = require('accounting');

            // 示例數(shù)據(jù)
            var CARS = [{
                    name: "Ferrari FF",
                    horsepower: 660,
                    dollar_value: 700000,
                    in_stock: true
                },
                {
                    name: "Spyker C12 Zagato",
                    horsepower: 650,
                    dollar_value: 648000,
                    in_stock: false
                },
                {
                    name: "Jaguar XKR-S",
                    horsepower: 550,
                    dollar_value: 132000,
                    in_stock: false
                },
                {
                    name: "Audi R8",
                    horsepower: 525,
                    dollar_value: 114200,
                    in_stock: false
                },
                {
                    name: "Aston Martin One-77",
                    horsepower: 750,
                    dollar_value: 1850000,
                    in_stock: true
                },
                {
                    name: "Pagani Huayra",
                    horsepower: 700,
                    dollar_value: 1300000,
                    in_stock: false
                }
            ];

            // 練習 1:
            // ============
            // 使用 _.compose() 重寫下面這個函數(shù)。提示:_.prop() 是 curry 函數(shù)
            var isLastInStock = function(cars) {
                var last_car = _.last(cars);
                return _.prop('in_stock', last_car);
            };

            var isLastInStock = compose(_.prop('in_stock'), _.last)

            // 練習 2:
            // ============
            // 使用 _.compose()、_.prop() 和 _.head() 獲取第一個 car 的 name
            var nameOfFirstCar = undefined;
            var nameOfFirstCar = compose(_.prop('name'), _.head)

            // 練習 3:
            // ============
            // 使用幫助函數(shù) _average 重構(gòu) averageDollarValue 使之成為一個組合
            var _average = function(xs) {
                return reduce(add, 0, xs) / xs.length;
            }; // <- 無須改動

            var averageDollarValue = function(cars) {
                var dollar_values = map(function(c) {
                    return c.dollar_value;
                }, cars);
                return _average(dollar_values);
            };

            var averageDollarValue = compose(_average, map(function(c) {
                return c.dollar_value;
            }))

            // 練習 4:
            // ============
            // 使用 compose 寫一個 sanitizeNames() 函數(shù),返回一個下劃線連接的小寫字符串:
            //例如:sanitizeNames(["Hello World"]) //=> ["hello_world"]。

            var _underscore = replace(/\W+/g, '_'); //<-- 無須改動,并在 sanitizeNames 中使用它

            var sanitizeNames = undefined;
            var sanitizeNames = map(compose(toLowerCase, replace));
            var sanitizeNames = compose(map(toLowerCase), map(replace));

            // 彩蛋 1:
            // ============
            // 使用 compose 重構(gòu) availablePrices

            var availablePrices = function(cars) {
                var available_cars = _.filter(_.prop('in_stock'), cars);
                return available_cars.map(function(x) {
                    return accounting.formatMoney(x.dollar_value);
                }).join(', ');
            };

            
            var availablePrices = compose(join(', '), map(compose(accounting.formatMoney, _.prop('dollar_value'))), _.filter(_.prop('in_stock')))

            // 彩蛋 2:
            // ============
            // 重構(gòu)使之成為 pointfree 函數(shù)。提示:可以使用 _.flip()

            var fastestCar = function(cars) {
                var sorted = _.sortBy(function(car) {
                    return car.horsepower
                }, cars);
                var fastest = _.last(sorted);
                return fastest.name + ' is the fastest';
            };
            
            var fastestCar = compose(concat(' is the fastest'),_.prop('name'),_.last,_.sortBy(_.prop('horsepower'))) 
            

做完這組練習題,
確實能夠感受到curry和compose的魅力.
有了curry和compose之后, 似乎, 真的可以把所有代碼弄成函數(shù)調(diào)用的形式.
只不過, 要充分了解, 每個函數(shù)接收的參數(shù)類型, 返回類型
很有魅力.

自由定理

// 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));

容器

lift: 一個函數(shù)在調(diào)用的時候,如果被 map 包裹了,那么它就會從一個非 functor 函數(shù)轉(zhuǎn)換為一個 functor 函數(shù)。我們把這個過程叫做 lift。

Container

            // 第一種容器
            var Container = function(x) {
                this.__value = x;
            }

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

            // (a -> b) -> Container a -> Container b
            Container.prototype.map = function(f) {
                return Container.of(f(this.__value))
            }

            Container.of("bombs").map(concat(' away')).map(_.prop('length'))
            //=> Container(10)

            //觀察這句代碼, 他與 compose 有點類似,
            // compose是從右向左, 而map 是 從左向右
            // compose 是把 return 出來的值, 傳遞給下一個函數(shù)的 參數(shù)入口
            // 而 map 是return 一個 容器, 用調(diào)用的方式, 把參數(shù)傳進去.
            // 當然兩者有很明顯的不同
            // compose 返回的是函數(shù), compose的執(zhí)行本身 實際上是延遲執(zhí)行, 預留出一個參數(shù)入口
            // 而在上面這種情況, 使用map的時候, 實際上參數(shù)值已經(jīng)確定, 并且功能已經(jīng)執(zhí)行,
            // 不存在延遲執(zhí)行.

            // 這種鏈式調(diào)用方式, 作者似乎稱之為 點記法(dot notation syntax)

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));
            }

            var maybe = curry(function(x, f, m) { // 出錯時返回自定義的信息, 不執(zhí)行 f, 
                return m.isNothing() ? x : f(m.__value);
            });

Either

            // 第三種容器 Either
            var Left = function(x) {
                this.__value = x;
            }

            Left.of = function(x) {
                return new Left(x);
            }
            // 這又是一種奇特的 map, Left容器遇到多少個map, 多少個 f, 返回的都是同樣的值,
            // 讓所有f都失效.
            // 這根 Maybe.of(null) 稍微不同.雖然Maybe.of(null) 的 map 也會讓所有的map,f都失效.
            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));
            }

            //  getAge :: Date -> User -> Either(String, Number)
            var getAge = curry(function(now, user) {
                var birthdate = moment(user.birthdate, 'YYYY-MM-DD');
                if(!birthdate.isValid()) return Left.of("Birth date could not be parsed");
                return Right.of(now.diff(birthdate, 'years'));
            });
            //  either :: (a -> c) -> (b -> c) -> Either a b -> c
                        // either的作用是這樣的, 
                        //任何map中的f對Left都沒有作用,
                        // either就是希望, 遇到Left情況時, 能夠讓f產(chǎn)生作用的接口.
            var either = curry(function(f, g, e) {
                switch(e.constructor) {
                    case Left:
                        return f(e.__value);
                    case Right:
                        return g(e.__value);
                }
            });

            //  zoltar :: User -> _
            var zoltar = compose(console.log, either(id, fortune), getAge(moment()));

IO

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

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

            IO.prototype.map = function(f) {
                return new IO(_.compose(f, this.__value));
            }
            var IO = function(f) {
                this.unsafePerformIO = f;
            }

            IO.prototype.map = function(f) {
                return new IO(_.compose(f, this.unsafePerformIO));
            }
/ 這種方式,就把所有功能都推遲到最后再執(zhí)行,
/ map 執(zhí)行的結(jié)果不是f 的執(zhí)行, 而是 compose

Task

Promise

這一部分沒有詳細剖析,

Compose

Compose 這個容器是專門用來存放 容器的容器.
值是一個容器
Compose.map 返回的也是雙層的容器.

var Compose = function(f_g_x){
  this.getCompose = f_g_x;
}

Compose.prototype.map = function(f){
  return new Compose(map(map(f), this.getCompose));
}
/你會發(fā)現(xiàn), 這個f, 穿越了兩個容器.
感覺除非是很熟悉的人, 誰會這么用呢?

練習題

            require('../../support');
            var Task = require('data.task');
            var _ = require('ramda');

            // 練習 1
            // ==========
            // 使用 _.add(x,y) 和 _.map(f,x) 創(chuàng)建一個能讓 functor 里的值增加的函數(shù)

            var ex1 = undefined

            var ex1 = function(x) {
                compose(_.map(_.add(x)))
            }

            //練習 2
            // ==========
            // 使用 _.head 獲取列表的第一個元素
            var xs = Identity.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do']);

            var ex2 = undefined
            var ex2 = xs.map(_.head);

            // 練習 3
            // ==========
            // 使用 safeProp 和 _.head 找到 user 的名字的首字母
            var safeProp = _.curry(function(x, o) {
                return Maybe.of(o[x]);
            });

            var user = {
                id: 2,
                name: "Albert"
            };

            var ex3 = undefined

            var fisrtChart = safeProp("name", user).map(_.head);

            // 練習 4
            // ==========
            // 使用 Maybe 重寫 ex4,不要有 if 語句

            var ex4 = function(n) {
                if(n) {
                    return parseInt(n);
                }
            };

            var ex4 = undefined
            var ex4 = function(n) {
                return Maybe.of(n).map(parseInt)
            }
            /我是一邊寫, 一邊覺得忽然發(fā)覺很神奇.. 真的把if,else 給干掉了.

            // 練習 5
            // ==========
            // 寫一個函數(shù),先 getPost 獲取一篇文章,然后 toUpperCase 讓這片文章標題變?yōu)榇髮?
            // getPost :: Int -> Future({id: Int, title: String})
            // 因為不了解Task, 我們暫且當成Promise來對待
            var getPost = function(i) {
                return new Task(function(rej, res) {
                    setTimeout(function() {
                        res({
                            id: i,
                            title: 'Love them futures'
                        })
                    }, 300)
                });
            }

            var ex5 = undefined
            var ex5 = getPost(i).fork(function(data) {
                return _.prop('title', data).toUpperCase() /
                    或者這樣 ?
                    return compose(toUpperCase, _.prop('title'))(data);
            })

            // 練習 6
            // ==========
            // 寫一個函數(shù),使用 checkActive() 和 showWelcome() 分別允許訪問或返回錯誤

            var showWelcome = _.compose(_.add("Welcome "), _.prop('name'))
            var checkActive = function(user) {
                return user.active ? Right.of(user) : Left.of('Your account is not active')
            }

            var ex6 = undefined

            var ex6 = compose(map(showWelcome), checkActive);

            // 練習 7
            // ==========
            // 寫一個驗證函數(shù),檢查參數(shù)是否 length > 3。如果是就返回 Right(x),否則就返回
            // Left("You need > 3")

            var ex7 = function(x) {
                return undefined // <--- write me. (don't be pointfree)
                return x > 3 ? Right(x) : Left("You need > 3")
            }

            // 練習 8
            // ==========
            // 使用練習 7 的 ex7 和 Either 構(gòu)造一個 functor,如果一個 user 合法就保存它,否則
            // 返回錯誤消息。別忘了 either 的兩個參數(shù)必須返回同一類型的數(shù)據(jù)。

            var save = function(x) {
                return new IO(function() {
                    console.log("SAVED USER!");
                    return x + '-saved';
                });
            }
            var ex8 = undefined
            
            var ex8 = function(x) {
                return undefined // <--- write me. (don't be pointfree)
                return x > 3 ? save(x) : IO.of('You need > 3')
            }
            // 還真是不知道對不對
            

一個 functor,只要它定義個了一個 join 方法和一個 of 方法,并遵守一些定律,那么它就是一個 monad。

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

文中說這個join的用途在于, 相同類型容器,多層嵌套時, 可以用來解除一層嵌套.
但從語義上來講, 或者原本的意義上來講,
join() 應(yīng)該是取值的意思,
而 of() 應(yīng)該是存值的意思.

monad

            // monad
            Maybe.prototype.join = function() {
                return this.isNothing() ? Maybe.of(null) : this.__value;
            }

            var join = function(m) {
                return m.join();
            }

            // 對其他類型容器都是一樣的
            IO.prototype.join = function() {
                return this.unsafePerformIO();
            }

            //  chain :: Monad m => (a -> m b) -> m a -> m b
            var chain = curry(function(f, m) {
                return m.map(f).join(); // 或者 compose(join, map(f))(m)
            });

下面這個用例, 不是很懂, 但顯得略微的屌, 還好之前接觸過跟Task 類似 的 Promise,
顯得沒那么陌生

// getJSON :: String -> {} -> Task(Error, JSON)
var getJSON = curry(function(url, params) {
return new Task(function(reject, result) {
$.getJSON(url, params, result).fail(reject);
});
});

/ 應(yīng)用chain
getJSON('/authenticate', {username: 'stale', password: 'crackers'})
  .chain(function(user) {
    return getJSON('/friends', {user_id: user.id});// 再次返回一個 Task, 如果不是chain, 而是map, 最后會嵌套
});
// Task([{name: 'Seimith', id: 14}, {name: 'Ric', id: 39}]);

例子2

            var $ = function(selector) {
                return new IO(function() {
                    return document.querySelectorAll(sel ector);
                });
            }
稍微改一下
            var querySelector = function(selector) {
                return new IO(function() {
                    return document.querySelectorAll(sel ector);
                });
            }

querySelector("input.username").chain(function(uname) {
  return querySelector("input.email").chain(function(email) {
    return IO.of(
      "Welcome " + uname.value + " " + "prepare for spam at " + email.value
    );
  });
});
// IO("Welcome Olivia prepare for spam at olivia@tremorcontrol.net");

/ 注意, IO里的chain 調(diào)用的 map/join , 而 map 里用的是compose

我勉強看懂了這一段代碼干了什么,
但感覺神乎其神!
這相當于compose的一種神乎其神的用法.
實際上你發(fā)現(xiàn), function(uname) {return querySelector("input.email")} 單指這一句代碼時,
你會發(fā)現(xiàn),壓根uname 和 里面的代碼沒鳥關(guān)系! 全都給了最后一個函數(shù).
這個東西, 很神奇. 對于我說不上來, 感到很自責.
我試著用compose來弄一下,

            / 現(xiàn)在我們試著把容器去掉
            
            var querySelector = function(selector) {
                    return function () {
                        return document.querySelectorAll(selector);
                    }
            }
            compose(function (uname) {
                return compose(function (email) {
                    return function () {return "Welcome " + uname + " " + "prepare for spam at " + email}
                },querySelector("input.email"))
            },querySelector("input.username"))

真是不好理解.
嚴格來講, 這不符合我認為的純函數(shù)的概念.
因為里層函數(shù)調(diào)用 uname 的時候, 實際上是跨越了一個參數(shù)入口,
也就是uname沒有通過 參數(shù)入口傳進來.
不過這種方式, 解決了一個問題,
那就是突破了compose 只能一次傳遞一個數(shù)據(jù)的限制.

或者想要突破一次只能傳一個參數(shù), 可以用 curry

            var some = curry(function (uname,email) {return "Welcome " + uname + " " + "prepare for spam at " + email})
            compose(compose(some,querySelector("input.username")),querySelector("input.email"))

這樣是否也能達到同樣的效果?

例子3

            Maybe.of(3).chain(function(three) {
                return Maybe.of(2).map(add(three));
            });
            // Maybe(5);
            
                        /相當于
            Maybe.of(3).map(function(three) {
                return Maybe.of(2).map(add(three));
            }).join();
            

                        /改成一般函數(shù), 應(yīng)該是相當于
            Maybe.of(3).map(add(2)})

比較一下, 比起一般函數(shù), 有什么優(yōu)點嘛? 
首先這個2 , 被容器包住了. 從某種角度來講, 我們希望所有的值,都被存在容器當中
也就是說, 我們希望所有的函數(shù)返回值的時候, 都包裹在容器當中.

換句話說, 之前我們把值放進容器中, 是用 Maybe.of() 或者 Maybe.map(f)的.
并且map(f)中的f, 則是接收一個非容器類型, 返回的是一個值.
而在這里, 我們希望 所有f返回的都是一個容器類型, 也就是把值全都放在容器當中

而,join,或者 chain, 就是用來處理這種情況的.

---
還是要問一句,這么做究竟有什么好處?

作者舉了一個對比的例子

應(yīng)用monad
// readFile :: Filename -> Either String (Future Error String)
// httpPost :: String -> Future Error JSON

//  upload :: String -> Either String (Future Error JSON)
var upload = compose(map(chain(httpPost('/uploads'))), readFile);

如果想讓 url 變成待定參數(shù)可以這樣
var upload =function (url) {
    return  compose(map(chain(httpPost('/uploads'))), readFile);
}

不應(yīng)用monad, 純指令式 完成上述功能
//  upload :: String -> (String -> a) -> Void
var upload = function(filename, callback) {
  if(!filename) {
    throw "You need a filename!";
  } else {
    readFile(filename, function(err, contents) {
      if(err) throw err;
      httpPost(contents, function(err, json) {
        if(err) throw err;
        callback(json);
      });
    });
  }
}

我們試著分析一下好處在哪?

  1. readfile 的 Either 對 filename 進行了一次判斷, 也就是去掉了if else?

  2. readfile 返回的值, 在調(diào)用map 時, Either 會進行測一次判斷?
    這里有疑問, readfile的Either到底是對1還是對2產(chǎn)生了作用?

  3. httpPost 返回的值, 又會得到一次 值判斷. 也去掉了一個if else

  4. 其實很明顯就能看到, 第一種比第二種代碼量要少.
    更關(guān)鍵的是, 用第一種, 也就是聲明式的方式, 可以減少設(shè)置變量.
    而第二種指令式, 就要設(shè)置很多變量.
    比如 filename,callback, err, contents,json
    而這些變量的設(shè)置,在后期改動的時候, 會比較麻煩.
    因為一處改, 所有引用這些變量的地方都要改.

練習題

            // 練習 1
            // ==========
            // 給定一個 user,使用 safeProp 和 map/join 或 chain 安全地獲取 sreet 的 name

            var safeProp = _.curry(function(x, o) {
                return Maybe.of(o[x]);
            });
            var user = {
                id: 2,
                name: "albert",
                address: {
                    street: {
                        number: 22,
                        name: 'Walnut St'
                    }
                }
            };

            var ex1 = undefined;

            var ex1 = compose(chain(safeProp('street')), safeProp('address'))
            //調(diào)用方式
            ex1(user);
            // 或者可以這樣
            var ex1 = mcompose(safeProp('street'), safeProp('address'));
            // 調(diào)用方式
            ex1(Maybe.of(user));

            // 練習 2
            // ==========
            // 使用 getFile 獲取文件名并刪除目錄,所以返回值僅僅是文件,然后以純的方式打印文件

            var getFile = function() {
                return new IO(function() {
                    return __filename;
                });
            }

            var pureLog = function(x) {
                return new IO(function() {
                    console.log(x);
                    return 'logged ' + x;
                });
            }

            var ex2 = undefined;
            // ex2 返回一個容器,
            var ex2 = getFile().chain(purelog)
            // 或者
            var ex2 = compose(chain(purelog), getFile)
            // ex2 返回一個 函數(shù), 執(zhí)行之后, 得到函數(shù)

            // 練習 3
            // ==========
            // 使用 getPost() 然后以 post 的 id 調(diào)用 getComments()
            var getPost = function(i) {
                return new Task(function(rej, res) {
                    setTimeout(function() {
                        res({
                            id: i,
                            title: 'Love them tasks'
                        });
                    }, 300);
                });
            }

            var getComments = function(i) {
                return new Task(function(rej, res) {
                    setTimeout(function() {
                        res([{
                                post_id: i,
                                body: "This book should be illegal"
                            },
                            {
                                post_id: i,
                                body: "Monads are like smelly shallots"
                            }
                        ]);
                    }, 300);
                });
            }

            var ex3 = undefined;

            var ex3 = getPost(i).fork(getComments)
            // 這是真不清楚

            // 練習 4
            // ==========
            // 用 validateEmail、addToMailingList 和 emailBlast 實現(xiàn) ex4 的類型簽名

            //  addToMailingList :: Email -> IO([Email])
            var addToMailingList = (function(list) {
                return function(email) {
                    return new IO(function() {
                        list.push(email);
                        return list;
                    });
                }
            })([]);

            function emailBlast(list) {
                return new IO(function() {
                    return 'emailed: ' + list.join(',');
                });
            }

            var validateEmail = function(x) {
                return x.match(/\S+@\S+\.\S+/) ? (new Right(x)) : (new Left('invalid email'));
            }

            //  ex4 :: Email -> Either String (IO String)
            var ex4 = undefined;
            
            var ex4 = mcompose(validateEmail,emailBlast,addToMailingList);
            // 或者
            var ex4 = compose(chain(validateEmail),chain(emailBlast),addToMailingList);

ap 函子 applicative functor

Container.prototype.ap = function(other_container) {
  return other_container.map(this.__value);
}

例子, 解決 2 + 3 的問題
第一種
// 使用可靠的 map 函數(shù)試試
var container_of_add_2 = map(add, Container.of(2));/ 返回一個函數(shù)
// Container(add(2))
Container.of(3).map(container_of_add_2)/ 完成 2 + 3

第二種
// 使用chain
Container.of(2).chain(function(two) {
  return Container.of(3).map(add(two));
});

第三種
// 使用ap
// add是個柯里化的函數(shù)
Container.of(2).map(add).ap(Container.of(3));
// Container(5)

ap 像不像 獨立定義的map, 我覺得很像.
ap變成 點記法之后, f 來源變成 this.__value, m 來源就是other_container

var map = function (f,m) { return m.map(f) }

感官上ap 和 chain的區(qū)別
首先ap接收的是一個容器, 更準確來講是, 讓兩個容器里的值碰頭.
而chain接收的是一個函數(shù), 一個返回容器的函數(shù).
具體兩者的應(yīng)用場景的區(qū)別, 作為一個小白, 還沒什么頭緒.

還有一個注意的地方是, 調(diào)用ap的容器, 里的值必須是函數(shù),
一個特性

F.of(x).map(f) == F.of(f).ap(F.of(x))
Maybe.of(add).ap(Maybe.of(2)).ap(Maybe.of(3));
// Maybe(5)

對比一下 
Maybe.of(2).map(Maybe.of(3).map(add).join())
// 似乎作者不希望有值是完全暴露在容器之外.至少起碼要包裹一層
// 所以 不怎么用join?

從使用的語義上來講,
map的調(diào)用, 給人的感覺是, 我有值, 我找函數(shù)
而ap的調(diào)用, 給人的感覺是, 我有函數(shù), 我找值.

作者說Task是,ap的用武之地

// Http.get :: String -> Task Error HTML

var renderPage = curry(function(destinations, events) { /* render page */  });

Task.of(renderPage).ap(Http.get('/destinations')).ap(Http.get('/events'))
// Task("<div>some page with dest and events</div>")

作者的原話是這樣的
兩個請求將會同時立即執(zhí)行,當兩者的響應(yīng)都返回之后,renderPage 就會被調(diào)用。這與 monad 版本的那種必須等待前一個任務(wù)完成才能繼續(xù)執(zhí)行后面的操作完全不同。本來我們就無需根據(jù)目的地來獲取事件,因此也就不需要依賴順序執(zhí)行。
作者的意思是, Http.get('/destinations') 和 Http.get('/events') 兩個動作并行執(zhí)行
但是我完全不理解,怎么做到的。

因為 Task.of(renderPage).ap(Http.get('/destinations')).ap(Http.get('/events'))
相當于, Http.get('/events').chain(Http.get('/destinations').map(renderPage))

就算是想成 Promise 也無法理解

            new Promise(function (res,rej) {
                setTimeout(function () {
                    console.log(123)
                    res(1234)
                },10000)
            }).then(function (data) {
                console.log(data);
                new Promise(function (res,rej) {
                setTimeout(function () {
                    console.log(223)
                },2000)
            })
            })

如果Task.map 相當于 Promise.then
那么上面的兩個ap,里包含兩個 map,
相當于 Promise 兩個then
如果像上面這樣模擬兩個異步任務(wù),
就會發(fā)現(xiàn),
只有在第一個異步任務(wù)完成,返回結(jié)果之后, 才會執(zhí)行第二個異步任務(wù)

那task到底是怎么實現(xiàn)的?
怎么做到.兩個異步任務(wù)并行發(fā)出?
其實兩個任務(wù)并行發(fā)出不是難事,
但按照作者的意思是, 當兩個異步都返回結(jié)果時, 才會執(zhí)行.

但到底怎么做到的? 是類似Pormiseall? 不能吧?

我還真是笨
百度搜不到 Task, 可以直接上github上 搜索啊
Data.Task源碼
Data.Task 函子 源碼 簡書
源碼雖然搞到手, 還是先等等再看吧, 看著有點頭疼..

pointFree版ap

var liftA2 = curry(function(f, functor1, functor2) {
  return functor1.map(f).ap(functor2);
});

var liftA3 = curry(function(f, functor1, functor2, functor3) {
  return functor1.map(f).ap(functor2).ap(functor3);
});

使用

liftA2(add, Maybe.of(2), Maybe.of(3));
// Maybe(5)

liftA2(renderPage, Http.get('/destinations'), Http.get('/events'))
// Task("<div>some page with dest and events</div>")

liftA3(signIn, getVal('#email'), getVal('#password'), IO.of(false));
// IO({id: 3, email: "gg@allin.com"}

免費開瓶器, 也就是 各種接口的互相轉(zhuǎn)化

之前,我們是先定義 map, 和of
由這兩個衍生出 ap
            X.of = function (x) {
                return new X(x)
            }
                        
            X.prototype.map = function (f) {
                return X.of(f(this.__value))
            }

根據(jù)這兩個, 我們推導出 ap
            X.prototype.ap = function (m) {
                return m.map(this.__value);
            }                        

現(xiàn)在反過來, 我們先定義 of 和 ap  然后推導出map
用作者的話來說就是           // 從 of/ap 衍生出的 map
            X.of = function (f) {
                return new X(f)
            }
            X.prototype.ap = function (m) {/我們?yōu)榱四M的徹底. ap的定義不用map
                return X.of(this.__value(m.__value))
            }
由此推導出map
            X.prototype.map = function(f) {
                return this.constructor.of(f).ap(this);
            }

同樣的意思, 
我們之前是先定義 of, map, join , 然后推導出 chain
            X.prototype.join = function () {
                return this.__value;
            }
然后推導出chain
            X.prototype.chain = function (f) {
                return this.map(f).join();
            }

返回來, 我們可以先定義chain, 由chain, 和 of 推導出 map
用作者的話來說// 從 chain 衍生出的 map
先定義chain
            X.prototype.chain = function (f) {/寫完我也懵逼了, 好簡單.
                return f(this.__value)
            }

然后推到出 map
X.prototype.map = function(f) {
  var m = this;
  return m.chain(function(a) {
    return m.constructor.of(f(a));
  });
}
// 簡化之后就是, return this.constructor.of(f(this.__value));/ 這就跟我們之前理解的map 是一樣的

// 從 chain/map 衍生出的 ap
X.prototype.ap = function(other) {
  return this.chain(function(f) {
    return other.map(f);
  });
};
/簡化之后就是, return other.constructor.of(this.__value(other.__value))
/ 這就跟我們之前對ap 的理解是一樣的, 取出前面的f 和后面的值見面.

作者說, ap比chain優(yōu)點在于能夠并行,
我實在不懂, ap為什么能夠并行?

  var tOfM = compose(Task.of, Maybe.of);

  liftA2(_.concat, tOfM('Rainy Days and Mondays'), tOfM(' always get me down'));
  // Task(Maybe(Rainy Days and Mondays always get me down))
這我就有點不理解了,
除非這個concat 不是我知道的那個concat, 否則這是不成立的.
因為會執(zhí)行 concat(Maybe.of(),Maybe.of()) , 如果我我知道的concat, 那這個肯定不成立.

練習題

            require('./support');
            var Task = require('data.task');
            var _ = require('ramda');

            // 模擬瀏覽器的 localStorage 對象
            var localStorage = {};
            // localStorage 對象? 什么意思? localStrorage 對象有什么特征嘛?
            // 不會..

            // 練習 1
            // ==========
            // 寫一個函數(shù),使用 Maybe 和 ap() 實現(xiàn)讓兩個可能是 null 的數(shù)值相加。

            //  ex1 :: Number -> Number -> Maybe Number
            var ex1 = function(x, y) {
                return Maybe.of(x).ap(Mapbe.of(y))
            };
            // 這也是挺神奇的. 對 x,y 進行了空值檢查. 只是返回值就是個容器了

            // 練習 2
            // ==========
            // 寫一個函數(shù),接收兩個 Maybe 為參數(shù),讓它們相加。使用 liftA2 代替 ap()。

            //  ex2 :: Maybe Number -> Maybe Number -> Maybe Number
            var ex2 = undefined;

            var add = curry(function(x, y) {
                return x + y;
            })
            // 是這樣嘛?
            var ex2 = function(m1, m2) {
                return liftA2(add, m1, m2);
            }

            // 練習 3
            // ==========
            // 運行 getPost(n) 和 getComments(n),兩者都運行完畢后執(zhí)行渲染頁面的操作。(參數(shù) n 可以是任意值)
            var makeComments = _.reduce(function(acc, c) {
                return acc + "<li>" + c + "</li>"
            }, "");
            var render = _.curry(function(p, cs) {
                return "<div>" + p.title + "</div>" + makeComments(cs);
            });

            function getComments(i) {// 話說這個i 有個屁用?
                return new Task(function(rej, res) {
                    setTimeout(function() {
                        res(["This book should be illegal", "Monads are like space burritos"]);
                    }, 300);
                });
            }

            function getPost(i) {
                return new Task(function(rej, res) {
                    setTimeout(function() {
                        res({
                            id: i,
                            title: 'Love them futures'
                        });
                    }, 300);
                });
            }

            //  ex3 :: Task Error HTML
            var ex3 = undefined;

            // 不懂..... 
            var ex3 = liftA2(render, getPost(2), getComments(6));
            // 只能這么理解了. 不過感覺好奇怪.
            // 我八輩子可能都無法應(yīng)用這種代碼

            // 練習 4
            // ==========
            // 寫一個 IO,從緩存中讀取 player1 和 player2,然后開始游戲。

            localStorage.player1 = "toby";
            localStorage.player2 = "sally";

            var getCache = function(x) {
                return new IO(function() {
                    return localStorage[x];
                });
            }
            var game = _.curry(function(p1, p2) {
                return p1 + ' vs ' + p2;
            });

            //  ex4 :: IO String
            var ex4 = undefined;

            var ex4 = IO.of(game).ap(getCache('player1')).ap(getCache('player2'));
            // 或者
            var ex4 = liftA2(game, getCache('player1'), getCache('player2'));
            // 如果用 monad 呢?

            var ex4 = IO.of(game).chain(function(f) {
                return getCache('player1').map(f)
            }).chain(function(f) {
                return getCache('player2').map(f)
            })
            // 把我自己都弄暈了.

另外的一些補充

至此, 把 js 函數(shù)式編程指南看來三遍.
用時10天. 我對我的效率感到稍許絕望. 還是自控力不行的表現(xiàn).
好在, 即使效率是致命傷, 但函數(shù)式編程指南本身的閱讀, 對我是一種腦洞大開的體驗.
只能說, 大概明白是怎么回事了. 只能算稍微理解了.遠遠談不上深入理解, 也談不上應(yīng)用.
其實初衷只是想要解開我對函數(shù)的一些疑惑.
函數(shù)這個我經(jīng)常用, 卻總是無法完全掌握用法的東西.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容