拉勾大前端的筆記捅位,僅作為學(xué)習(xí)記錄
課程介紹
- 為什么學(xué)習(xí)函數(shù)式編程怒见,以及什么是函數(shù)編程
- 函數(shù)式編程的特性(純函數(shù),柯里化禽车,函數(shù)組合)
- 函數(shù)式編程的應(yīng)用場(chǎng)景
- 函數(shù)式編程庫 Lodash
為什么學(xué)些函數(shù)式編程
隨著React 的流行備受關(guān)注寇漫,Vue3.0也開始擁抱函數(shù)式,函數(shù)式可以拋棄this殉摔,打包過程中更好的利用tree shaking 過濾無用代碼州胳,方便測(cè)試,方便并行處理逸月,有很多庫可以幫助我們進(jìn)行函數(shù)開發(fā) Lodash,underscorce,ramda
什么是函數(shù)編程
函數(shù)式編程是編程范式之一栓撞,常說的還有面向?qū)ο蠛兔嫦蜻^程
面向?qū)ο?/strong>:把現(xiàn)實(shí)世界中的事物抽象成程序世界中的類和對(duì)象,通過封裝碗硬、繼承瓤湘、多態(tài)演示事物之間的聯(lián)系
函數(shù)式編程: 把現(xiàn)實(shí)世界中事物和事物之間的聯(lián)系抽象成程序世界,是對(duì)運(yùn)算過程的抽象恩尾,像純函數(shù)(相同的輸入得到相同的輸出) eg. y=sin(x-0)
函數(shù)式編程--前置知識(shí)
- 函數(shù)是一等公民
- 高階函數(shù)
- 閉包
函數(shù)是一等公民
- 函數(shù)可以存儲(chǔ)在變量中
- 函數(shù)可以作為參數(shù)
- 函數(shù)可以作為返回值
在JavaScript中函數(shù)式一個(gè)普通的對(duì)象 (可以通過 new Function)弛说,我們可以把函數(shù)存儲(chǔ)在變量/數(shù)組中,它還可以作為另一個(gè)函數(shù)的參數(shù)和返回值翰意,甚至我們可以在程序運(yùn)行的時(shí)候通過new Function(‘a(chǎn)lert(1)’)來構(gòu)造一個(gè)新的函數(shù)
把函數(shù)賦值給變量
let fn = function(){
console.log(`hello`)
}
fn()
高階函數(shù)
- 高階函數(shù)(Higher-order function)
- 可以把函數(shù)作為參數(shù)傳遞給另一個(gè)函數(shù)
- 可以把函數(shù)作為另一個(gè)函數(shù)的返回結(jié)果
函數(shù)作為參數(shù)傳遞給另一個(gè)函數(shù)
// 模擬forEach
function forEach(arr,fn){
for( let i = 0; i < arr.length; i++ ){
fn(arr[i])
}
}
//模擬filter
function filter(arr,fn){
const res = []
for( let i = 0; i < arr.length; i++ ){
if(fn(arr[i])){
res.push(arr[i])
}
}
return res
}
函數(shù)作為返回值
function makeFn(){
let msg = `hello function`
return function(){
console.log(msg)
}
}
// 模擬once 使用場(chǎng)景:支付場(chǎng)景
function once(fn){
const done = false
return function(){
if(!done){
done = true
return fn.apply(this, arguments) // 用戶調(diào)用的時(shí)候可能會(huì)傳遞參數(shù)剃浇,所以把調(diào)用當(dāng)前函數(shù)的參數(shù)傳遞給fn
}
}
}
let pay = once(function(money){
console.log(`支付了 ${money} RMB`)
})
使用高階函數(shù)的意義
- 抽象可以幫我們屏蔽細(xì)節(jié)巾兆,只需要關(guān)注于我們的目標(biāo)
- 高階函數(shù)是用來抽象通用的問題
常見的高階函數(shù)
// 模擬map
const map = (arr,fn)=>{
const results = []
for(const value of arr ){
results.push(fn(value))
}
return results
}
// 模擬every
const every = (array, fn) => {
let result = true
for(let value of array){
result = fn(value)
if(!result){
break
}
}
return result
}
// 模擬some
const some = (array, fn) => {
let result = false
for(let value of array){
result = fn(value)
if(!result){
break
}
}
return result
}
閉包
閉包的概念
閉包(Closure): 函數(shù)和其周圍的狀態(tài)(詞法環(huán)境)的引用捆綁在一起行程閉包
- 可以在另一個(gè)作用于中調(diào)用一個(gè)函數(shù)的內(nèi)部函數(shù)并訪問到改函數(shù)的作用域中的成員
function makeFn (){
let msg = `Hello`
return function(){
console.log(msg)
}
}
const fn = makeFn() // 因?yàn)橥獠繉?duì)內(nèi)部的函數(shù)有引用,所以內(nèi)部的msg變量不能被釋放掉
fn()
- 閉包的本質(zhì):函數(shù)在執(zhí)行的時(shí)候會(huì)被放到一個(gè)執(zhí)行棧上,當(dāng)函數(shù)執(zhí)行完畢后虎囚,會(huì)被從執(zhí)行棧中移出角塑,**但是堆上的作用域成員因?yàn)楸煌獠恳貌荒茚尫?*,因此內(nèi)部函數(shù)依然可以訪問外部函數(shù)成員
閉包 的案例
// 求平方
function makePower (power){
return function (number){
Math.pow(number,power)
}
}
const power2 = makePower(3) // 求3的平方
// 求員工的工資
function makeSalary (base){
return function (performance){
return base + performance
}
}
純函數(shù)
純函數(shù)的概念
純函數(shù) 相同的輸入永遠(yuǎn)會(huì)得到相同的輸出淘讥,而且沒有任何可觀察的副作用
- 純函數(shù)就類似于數(shù)學(xué)中的函數(shù)圃伶,用來描述輸入和輸出之間的關(guān)系,y=f(x)
- lodash 是一個(gè)純函數(shù)的功能庫,提供了對(duì)數(shù)組蒲列、數(shù)字窒朋、對(duì)象、字符串蝗岖、函數(shù)等操作的一些方法
- 數(shù)組中的slice 和 splice 分別是:純函數(shù)和不純函數(shù)
- slice 返回?cái)?shù)組中的指定部分侥猩,不會(huì)改變?cè)瓟?shù)組
- splice 對(duì)數(shù)組進(jìn)行操作返回?cái)?shù)組,會(huì)改變?cè)瓟?shù)組
let array = [1,2,3,4,5]
// 純函數(shù)
console.log(array.slice(0,3)) // [1,2,3]
console.log(array.slice(0,3)) // [1,2,3]
// 不純函數(shù)
console.log(array.splice(0,3)) // [1,2,3]
console.log(array.splice(0,3)) // [4,5]
- 函數(shù)式編程不會(huì)保留計(jì)算中間的結(jié)果抵赢,所以變量是不可變的(無狀態(tài)的)
- 我可以把一個(gè)函數(shù)的執(zhí)行結(jié)果交給另一個(gè)函數(shù)去處理
Lodash中提供的純函數(shù)
const array = ['jack','lucy','mack','nike']
console.log(_.first(array)) // jack
console.log(_.last(array)) // nike
console.log(_.toUpper(_.first(array))) JACK
純函數(shù)的好處
- 可緩存
因?yàn)橄嗤妮斎胧冀K會(huì)有相同的結(jié)果欺劳,所以可以把純函數(shù)的結(jié)果緩存起來
使用緩存的原因:如果有個(gè)函數(shù)調(diào)用起來特別耗時(shí),并且需要多次調(diào)用铅鲤,可以在第一次調(diào)用的時(shí)候把結(jié)果緩存起來划提,第二次調(diào)用直接返回緩存的結(jié)果
// lodash中的記憶函數(shù)
const _ = require('lodash')
function getArea(r){
console.log(r)
return Math.PI *r *r
}
let getAreaWithMemory = _.memoize(getArea)
console.log(getAreaWithMemory(4))
console.log(getAreaWithMemory(4))
console.log(getAreaWithMemory(4))
// 4
// 圓的面積
// 圓的面積
// 圓的面積
模擬memoize
function memoize(f){
const cache = {}
return function(){
let key = JSON.stringfy(arguments)
cache[key] = cache[key] || f.apply(f,arguments)
return cache[key]
}
}
- 可測(cè)試
因?yàn)榧兒瘮?shù)始終有輸入輸出,讓測(cè)試更方便 - 并行處理
- 在多線程環(huán)境下并行操作共享的內(nèi)存數(shù)據(jù)可能會(huì)發(fā)生意外情況
- 純函數(shù)不需要訪問共享的內(nèi)存數(shù)據(jù)邢享,所以在并行環(huán)境下可以任意運(yùn)行純函數(shù)
純函數(shù)的副作用
- 純函數(shù):對(duì)于相同的輸入永遠(yuǎn)會(huì)得到相同的輸出鹏往,而且沒有任何可觀察的副作用
// 不純的
let mini = 18
function checkAge(age){
return age >= mini
}
// 純的(有硬編碼,后續(xù)通過柯里化解決)
function checkAge (age){
let mini = 18
return age >= mini
}
副作用讓一個(gè)函數(shù)變得不純(如上例)骇塘,純函數(shù)是根據(jù)相同的輸入返回相同的輸出伊履,如果函數(shù)依賴于外部的狀態(tài)就無法保成輸出相同,就會(huì)帶來副作用
副作用的來源:
- 配置文件
- 數(shù)據(jù)庫
- 獲取用戶的輸入
- ...
所有的外部交互都有可能帶來副作用款违,副作用也使得方法通用性下降不適合拓展和可重用性湾碎,同時(shí)副作用會(huì)給程序帶來安全應(yīng)還給程序帶來不確定性,但是副作用不可能完全禁止奠货,盡可能控制他們?cè)诳煽胤秶鷥?nèi)發(fā)生
柯里化
柯里化概念
- 當(dāng)一個(gè)函數(shù)有多個(gè)參數(shù)的時(shí)候先傳遞一部分參數(shù)調(diào)用它(這部分參數(shù)以后永遠(yuǎn)不變)
- 然后返回一個(gè)新的函數(shù)接受剩余的參數(shù)介褥,返回結(jié)果
使用柯里化解決上一個(gè)案例中硬編碼的問題
// 存在硬編碼的函數(shù)
function checkAge(age){
let min = 18
return age >= mini
}
// 普通的純函數(shù)
function checkAge( min, age){
return age >= min
}
// 函數(shù)的柯里化
function checkAge(min){
return function (age){
return age >= mini
}
}
// ES寫法
let checkAge = min => (age=> age >= min)
let checkAge18 = checkAge(18)
checkAge18(20) // true
Lodash中的柯里化函數(shù)
_.curry(func)
- 功能:創(chuàng)建一個(gè)函數(shù),該函數(shù)接收一個(gè)或多個(gè)func的參數(shù)递惋,如果func所需要的參數(shù)都被提供則執(zhí)行func并返回執(zhí)行結(jié)果柔滔,否則繼續(xù)執(zhí)行返回該函數(shù)并等待接收剩余的參數(shù)
- 參數(shù): 需要柯里化的函數(shù)
- 返回值: 柯里化后的函數(shù)
// lodash 中 curry 的基本使用
// 柯里化可以把任意多元的函數(shù)轉(zhuǎn)化為一元的函數(shù)
const _ = require('lodash')
function getSum(a,b,c){
return a + b + c
}
const curried = _.curry(getSum)
console.log(curried(1,2,3)) // 6 因?yàn)閭鬟f了func所需的所有參數(shù),func會(huì)被執(zhí)行并返回執(zhí)行結(jié)果
console.log(curried(1)(2,3)) //當(dāng)傳遞了一個(gè)函數(shù)后萍虽,會(huì)返回一個(gè)新的函數(shù)并等待接收剩余參數(shù)
console.log(curried(1,2)(3))
柯里化的案例
// 匹配字符串中的所有空白字符
// 之后還需要提取字符串中的數(shù)字
// 面向過程的方式
' '.match(/\s+/g)
' '.match(/\d+/g)
// 純函數(shù)
function match (reg,str) {
return str.match(reg)
}
// 柯里化處理
const _ = require('lodash')
const match = _.curry(function(reg,str){
return str.match(reg)
})
const haveSpace = match(/\s+/g)
const haveNumber = match(/\d+/g)
const filter = _.curry((func,array) => array.filter(func))
const findSpace = filter(haveSpace)
findSpace(['hello world','jkl'])
柯里化實(shí)現(xiàn)原理
function curry(func){
return function curriedFn(...args){
// 判斷實(shí)參和形參的個(gè)數(shù)
if(args.length < func.length){
return function (){
return curriedFn(...args.concat(Array.form(arguments)))
}
}
return func(...args)
}
}
柯里化總結(jié)
- 柯里化可以讓我們給一個(gè)函數(shù)傳遞較少的參數(shù)得到一個(gè)已經(jīng)記住了某些固定參數(shù)的新函數(shù)
- 這是一種對(duì)函數(shù)參數(shù)的‘緩存’
- 讓函數(shù)變得更靈活睛廊,讓函數(shù)的粒度更小
- 可以把多元函數(shù)轉(zhuǎn)化為一元函數(shù),可以組合使用參數(shù)產(chǎn)生強(qiáng)大的功能
函數(shù)組合 Compose
純函數(shù)和柯里化很容易寫出洋蔥代碼h(g(f(x)))
- 獲取數(shù)組的最后一個(gè)元素再轉(zhuǎn)換成大寫字母
_.toUpper(_.first(_.reverse(array)))
函數(shù)組合可以讓我們把細(xì)粒度的函數(shù)重新組合生成一個(gè)新的函數(shù)
管道
下面這張圖表示程序中使用函數(shù)處理數(shù)據(jù)的過程杉编,給fn函數(shù)輸入?yún)?shù)a超全,返回結(jié)果b,可以想到a數(shù)據(jù)通過管道得到了b數(shù)據(jù)
當(dāng)函數(shù)fn比較復(fù)雜的時(shí)候咆霜,我們可以把函數(shù)fn拆成多個(gè)小函數(shù),此時(shí)多了中間運(yùn)算過程產(chǎn)生的m和n
下面這張圖可以想象成把fn這個(gè)管道拆分成3個(gè)管道f1,f2,f3,數(shù)據(jù)a通過管道f3得到結(jié)果m,m再通過管道f2得到結(jié)果n,n通過管道f1得到最終的結(jié)果b
fn = compose(f1,f2,f3)
b = fn(a)
函數(shù)組合概念
函數(shù)組合(compose) 如果一個(gè)函數(shù)經(jīng)過多個(gè)函數(shù)處理才能得到最終值嘶朱,這個(gè)時(shí)候可以把中間過程的函數(shù)合并成一個(gè)函數(shù)
- 函數(shù)就像是一個(gè)數(shù)據(jù)的管道蛾坯,函數(shù)組合就是把這些管道連接起來,讓數(shù)據(jù)穿過多個(gè)管道形成最終結(jié)果
- 函數(shù)組合默認(rèn)是從右到左執(zhí)行
// 組合函數(shù)演示
// 獲取數(shù)組的最后一元素
function compose(f, g){
return function(value){
f(g(value))
}
}
function reverse(array){
return array.reverse()
}
function first(array){
return array[0]
}
const last = compose(first, reverse)
console.log(last([1,2,3,4]))
Lodash中的組合函數(shù)
- lodash中提供組合函數(shù)flow()或者flowRight(),他們可以組合多個(gè)函數(shù)
- flow()是從左到右運(yùn)行
- flowRight()是從右到左運(yùn)行疏遏,使用更多一些
// lodash 中的 _.flowRight()
const _ = require('lodash')
const reverse = array => array.reverse()
const first = array => array[0]
const toUpper = array => array.toUpperCase()
const fn = _.flowRight(toUpper, first, reverse)
console.log(fn(['one, 'two', 'three']))
flowRight的實(shí)現(xiàn)原理
function compose(...args){
return function(value){
return args.reverse().reduce(function(acc,fn){
return fn(acc)
}, value)
}
}
// es6改寫
const compose = (...args) => value => args.reverse().reduce( (acc,fn )=> fn(acc), value)
函數(shù)組合
結(jié)合律
函數(shù)組合要滿足結(jié)合律
- 我們既可以把g和h組合脉课,還可以把f和g組合,結(jié)果都是一樣的
// 結(jié)合律
const f = _.flowRight(_.toUpper, _.first, _.reverse)
const f = _.flowRight(_.flowRight(_.toUpper,_.first),_.reverse)
const f = _.flowRight(_.toUpper,_.flowRight(_.first, _.reverse))
調(diào)試
// NEVER SAY DIE --> never-say-die
// 思路:通過過濾空格财异,將字符串轉(zhuǎn)化為數(shù)組倘零,把數(shù)組的每一項(xiàng)變?yōu)樾懀ㄟ^字符串‘-’分割數(shù)組
const split = _.curry((sep,str) => _.split(str,sep))
const map = _.curry((fn,array)=> _.map(array,fn))
const join = _.curry((spe,array)=>_.join(array,spe))
// 調(diào)試的時(shí)候可以寫一個(gè)輔助函數(shù)戳寸,看上一個(gè)函數(shù)的執(zhí)行結(jié)果呈驶,并把執(zhí)行結(jié)果返回給下一個(gè)待執(zhí)行的函數(shù)
const log = v => {
console.log(v)
return v
}
// 改造log
const trace = _.curry((tag,v)=>{
console.log(tag,v)
return v
})
const fn = _.flowRight( join('-'), trace('在map之后打印的'),map(_.toLower), log, split(' '))
console.log('NEVER SAY DIE')
Lodash中的FP模塊
- lodash的fp模塊提供了實(shí)用的對(duì)函數(shù)式編程友好的方法
- 提供了不可變 已經(jīng)被柯里化的(auto-curried)并且遵循函數(shù)有先(iteratee-first) 數(shù)據(jù)滯后(data-last)的方法
const _ = require('lodash')
// lodash 模塊中數(shù)據(jù)優(yōu)先函數(shù)置后
_.map(['a','b','c'],_.toUpper)
_.split('hello world', ' ')
const fp = require('lodash/fp')
// fp模塊中函數(shù)優(yōu)先,數(shù)據(jù)置后
fp.map(fp.toUpper,['a','b','c'])
fp.map(fp.toUpper)(['a','b','c'])
fp.split(' ', 'hello world')
fp.split(' ')('hello world')
// 通過fp改造之前的Never SAY DIE -> nerver-say-die 案例
const fp = require('lodash/fp')
const fn = fp.flowRight(fp.join('-'),fp.map(fp.toLower,fp.split(' ')))
lodash 中的map和fp中的map區(qū)別
- lodash中的map后面的function接收三個(gè)參數(shù) value:any, index|key, array
- fp中的map中function只接收一個(gè)參數(shù) value:any
Point Free
定義:我們可以把數(shù)據(jù)處理的過程定義成與數(shù)據(jù)無關(guān)的合成運(yùn)算疫鹊,不需要用到代表數(shù)據(jù)的那個(gè)參數(shù)袖瞻,只需要簡(jiǎn)單的把運(yùn)算步驟合成到一起,在使用這種模式之前我們需要定義一些輔助的基本運(yùn)算函數(shù)
- 不需要指明處理的數(shù)據(jù)
- 只需要合成運(yùn)算的過程
- 需要定義一些輔助的基本運(yùn)算函數(shù)
// Hello World -> hello_world
const fp = require('lodash/fp')
const f = fp.flowRight(fp.replace(/s+/g, '_'), fp.toLower)
案例
// world wild web ==> W. W. W.
const firstLetterToUpper = fp.flowRight(fp.join('. '), fp.map(fp.first), fp.map(fp.toUpper), fp.split(' '))
// 改造
const firstLetterToUpper = fp.flowRight(fp.join('. '), fp.map(fp.flowRight(fp.first,fp.toUpper)), fp.split(' '))
函子Functor
為什么學(xué)習(xí)函子
到目前為止已經(jīng)學(xué)習(xí)了函數(shù)式編程的一些基礎(chǔ)订晌,但是我們還沒有演示在函數(shù)式編程中如何把副作用控制到可控范圍內(nèi)、異常處理蚌吸、異步操作等
什么是Functor
- 容器:包含值和值得變形關(guān)系(這個(gè)變形關(guān)系就是函數(shù))
- 函子:是一個(gè)特殊的容器锈拨,通過一個(gè)普通的對(duì)象來實(shí)現(xiàn),該對(duì)象具有map方法羹唠,map方法可以運(yùn)行一個(gè)函數(shù)奕枢,對(duì)值進(jìn)行處理(變形關(guān)系)
// Functor 函子
class Container { // 創(chuàng)建函子類
constructor (value) {
this._value = value // 接收一個(gè)value,存儲(chǔ)在內(nèi)部
}
map(fn){ // 內(nèi)部有個(gè)map方法接收一個(gè)純函數(shù),處理完數(shù)據(jù)并返回一個(gè)函子對(duì)象
return new Container(fn(this._value))
}
}
// 調(diào)用
let r = new Container(5)
.map(x => x + 1)
.map(x => x * x)
console.log(r) // 36
// 對(duì)上面的class 類進(jìn)行改造佩微,封裝靜態(tài)方法of, new Container創(chuàng)建函子對(duì)象
class Container {
static of (value){
return new Container(value)
}
constructor (value) {
this._value = value // 接收一個(gè)value,存儲(chǔ)在內(nèi)部屬性_value中
}
map(fn){ // 內(nèi)部有個(gè)map方法接收一個(gè)純函數(shù)缝彬,處理完數(shù)據(jù)并返回一個(gè)函子對(duì)象
return Container.of(fn(this._value))
}
}
函子總結(jié)
- 函數(shù)式編程的運(yùn)算不直接操作值,而是由函子完成
- 函子就是一個(gè)實(shí)現(xiàn)了map契約的對(duì)象
- 我們可以把函子想象成一個(gè)盒子哺眯,這個(gè)盒子里封裝了一個(gè)值
- 想要處理盒子中的值谷浅,我們需要給盒子的map方法傳遞一個(gè)處理值的函數(shù)(純函數(shù)),由這個(gè)函數(shù)對(duì)值進(jìn)行處理
- 最終map方法返回一個(gè)包含新值得盒子(函子)
// 演示 null undefined 的問題
Container.of(null)
.map(x => x.toUpperCase())
// 傳遞null會(huì)報(bào)錯(cuò)奶卓,不符合純函數(shù)的特征(相同的輸入會(huì)有相同的輸出)
MayBe函子
- 我們?cè)倬幊痰倪^程中可能會(huì)處理很多錯(cuò)誤一疯,需要對(duì)這些錯(cuò)誤做相應(yīng)的處理
- MayBe 函子的作用就是可以對(duì)外部的空值情況做處理(控制副作用在允許的范圍)
class MayBe {
static of(value){
return new MayBe(value)
}
constructor(value){
this._value = value
}
map(fn){
return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(value))
}
isNothing(){
return this._value === null || this._value === undefined
}
}
// MayBe 函子的問題
let r = MayBe.of('hello world')
.map(x => x.toUpperCase())
.map(null)
.map(x => x.split(' '))
console.log(r) // MayBe{_value: null }
// 不知道是什么地方出現(xiàn)了null
Either函子
- Either兩者中的任何一個(gè),類似于 if...else 的處理
- 異常會(huì)讓函數(shù)變得不存夺姑,Either函子可以用來做異常處理
class Left{
static of(value){
return new Left(value)
}
constructor(value){
this._value = value
}
map(fn){
return this
}
}
class Right{
static of(value){
return new Right(value)
}
constructor(value){
this._value = value
}
map(fn){
return Right.of(fn(this._value))
}
}
function parseJSON(str){
try{
return Right(JSON.parse(str))
}catch(e){
return Left({error:e.message})
}
}
const r = parseJSON('{name:zs}')
console.log(r) // 走Left
IO函子
- IO函子中的_value是一個(gè)函數(shù)墩邀,這里是把函數(shù)作為值來處理
- IO函子可以把不純的動(dòng)作存儲(chǔ)到_value中,延遲執(zhí)行這個(gè)不純的操作(惰性執(zhí)行)
- 把不純的操作交給調(diào)用者來處理
const fp = require('lodash/fp')
class IO {
static of(x){
return new IO(function(){
return x
})
}
constructor(fn){
this._value = fn
}
map(fn){
return new IO(fp.flowRight(fn,this._value))
}
}
// 調(diào)用
let r = IO.of(process).map(p => p.exexPath)
console.log(r._value)
Monad 函子
- 是可以變扁的Pointed函子盏浙,IO(IO(x))
- 一個(gè)函子如果具有join 和 of兩個(gè)方法眉睹,并遵守一些定律就是一個(gè)Monad
const fp = require('lodash/fp')
class IO {
static of(x){
return new IO(function(){
return x
})
}
constructor(fn){
this._value = fn
}
map(fn){
return new IO(fp.flowRight(fn,this._value))
}
join() {
return this._value()
}
flatMap(fn){
return this.map(fn).join()
}
}
let readFile = function(filename) {
return new IO(function(){
return fs.readFileSync(filename, 'utf-8')
})
}
let print = function (x) {
return new IO(function(){
console.log(x)
return x
})
}
let r = readFileSync('package.json')
.flatMap(print)