#### 函數(shù)式編程
#### 函數(shù)式編程總結(jié)
1. 認(rèn)識(shí)函數(shù)式編程
2. 函數(shù)復(fù)習(xí)
? ? (1)函數(shù)是一等公民
? ? (2)高階函數(shù)
? ? (3)閉包
3. 函數(shù)式編程基礎(chǔ)
? ? (1)lodash
? ? (2)純函數(shù)
? ? (3)柯里化
? ? (4)管道
? ? (5)函數(shù)組合
4. 函子
? ? (1)Functor
? ? (2)MayBe
? ? (3)Either
? ? (4)IO
? ? (5)Task(folktale)
? ? (6)Monad
#### 認(rèn)識(shí)函數(shù)式編程
1. 隨著React的流行收到越來愈多的關(guān)注, React高階組件使用函數(shù)式編程實(shí)現(xiàn)
2. Vue 3 開始擁抱函數(shù)式編程
3. 函數(shù)式編程可以拋棄this
4. 打包過程中可以更好的利用tree-shaking過濾無用代碼
5. 方便測(cè)試, 方便處理
6. 更多生態(tài)庫進(jìn)行函數(shù)式開發(fā): lodash、 underscore、ramda
#### 函數(shù)式編程概念
1. FP是編程范式之一? (面向過程、面向?qū)ο缶幊?
2. 面向?qū)ο缶幊痰乃季S方式: 把現(xiàn)實(shí)世界中的事物抽象成程序世界中的類和對(duì)象, 通過封裝、繼承、多態(tài)來演示事物事件的聯(lián)系
3. 函數(shù)式編程的思維方式: 把現(xiàn)實(shí)世界的事物和事物之間的聯(lián)系抽象到程序世界(對(duì)運(yùn)算過程進(jìn)行抽象)
? 程序本質(zhì): 根據(jù)輸入通過某種運(yùn)算獲得相應(yīng)的輸出
? 函數(shù)式編程中的函數(shù)是數(shù)學(xué)中的函數(shù)即映射關(guān)系
? 相同的輸入始終得到相同的輸出(純函數(shù))
? 函數(shù)式編程用來描述數(shù)據(jù)(函數(shù))之間的映射
#### 函數(shù)復(fù)習(xí)
#### 函數(shù)是一等公民 First-class Function
1. 函數(shù)是一個(gè)普通對(duì)象: 可以存儲(chǔ)到變量/數(shù)組中, 可以作為另一個(gè)函數(shù)的參數(shù)和返回值, 可以通過new Function()構(gòu)造一個(gè)新函數(shù)
****MDN中關(guān)于函數(shù)是一等公民的解釋只有三個(gè): (1)可以存儲(chǔ)于變量中(2)可以做為參數(shù)(3)可以作為返回值
****函數(shù)可以遞歸調(diào)用---不是函數(shù)是一等公民的佐證
2. 函數(shù)是一等公民是學(xué)習(xí)高階函數(shù)、柯里化的基礎(chǔ)
#### 高階函數(shù) Higher-order function
1. 函數(shù)可以作為參數(shù)傳遞給另一個(gè)參數(shù)
function forEach (array, fn){
? ? for(let i = 0;i < array.length; i++){
? ? ? fn(array[i])
? ? }
}
let arr = [1, 3, 4, 9, 10]
forEach(arr, function(item){
? ? console.log(item)
})
2. 函數(shù)可以作為一個(gè)函數(shù)的返回值
function makeFn(){
? ? let msg = 'Hello'
? ? return function(){
? ? ? ? console.log(msg)
? ? }
}
const fn = makeFn()
fn()
makeFn()()
#### 高階函數(shù)的意義
1. 抽象可以幫我們屏蔽細(xì)節(jié), 只需要關(guān)注于我們的目標(biāo)
2. 高階函數(shù)是用來抽象通用的問題
#### 常用的高階函數(shù) forEach map filter every some find/findIndex reduce等
// map
const map = (array, fn) => {
? ? let results = []
? ? for(let value of array){
? ? ? ? results.push(fn(value))
? ? }
? ? return results
}
let arr = [1,3, 4, 5]
arr = map(arr, item => item * item)
console.log(arr)
//every
const every = (array, fn) => {
? ? let result = true
? ? for(let value of array){
? ? ? ? result = fn(value)
? ? ? ? if(!result){
? ? ? ? ? ? break
? ? ? ? }
? ? }
? ? return result
}
let arr = [1, 3, 5, 5,6, 20]
let r = every(arr, v => v > 10)
console.log(r)
//some
const some = (array, fn) => {
? ? let result = false
? ? for(let value of array){
? ? ? ? result = fn(value)
? ? ? ? if(result){
? ? ? ? ? ? break
? ? ? ? }
? ? }
? ? return result
}
let arr = [1, 3, 5, 7]
let r = some(arr, v => v % 2 === 0)
console.log(r)
#### 閉包
1. 閉包: 函數(shù)和其周圍的狀態(tài)(詞法環(huán)境)的飲用捆綁在一起形成閉包;? 可以在另一個(gè)作用域中調(diào)用一個(gè)函數(shù)的內(nèi)部函數(shù)并訪問到趕海書的作用域中的成員
2. 閉包的本質(zhì): 函數(shù)在執(zhí)行的時(shí)候會(huì)放到一個(gè)執(zhí)行棧上, 當(dāng)函數(shù)執(zhí)行完畢之后會(huì)從執(zhí)行棧上移除, 但是堆上的作用域成員因?yàn)楸煌獠恳貌荒茚尫? 因此內(nèi)部函數(shù)依然可以訪問外部函數(shù)的成員
//案例一
function forEach (array, fn){
? ? for(let i = 0;i < array.length; i++){
? ? ? fn(array[i])
? ? }
}
let arr = [1, 3, 4, 9, 10]
forEach(arr, function(item){
? ? console.log(item)
})
//案例二
function makeFn(){
? ? let msg = 'Hello'
? ? return function(){
? ? ? ? console.log(msg)
? ? }
}
const fn = makeFn()
fn()
makeFn()()
#### 函數(shù)式編程基礎(chǔ)
#### 純函數(shù)
1. 相同的輸入永遠(yuǎn)會(huì)得到相同的輸出, **而且沒有任何可觀察的副作用**
? ? 副作用: 函數(shù)依賴于外部的轉(zhuǎn)臺(tái)就無法保證輸出相同, 就會(huì)帶來副作用
? ? 副作用來源:? (1) 配置文件
? ? ? ? ? ? ? ? (2) 數(shù)據(jù)庫
? ? ? ? ? ? ? ? (3) 獲取用戶的輸入
? ? ? ? --- 所有的外部交互都有可能代理副作用, 副作用也使得方法通用性下降, 不適合擴(kuò)展和可重用性,同時(shí)副作用會(huì)給程序中帶來安全隱患給程序帶來不確定性, 但是副作用不可能完全禁止, 僅能能控制他們?cè)诳煽胤秶鷥?nèi)發(fā)生
? ? ? ? --- 副作用讓函數(shù)變得不純
2. 函數(shù)式編程不會(huì)保留計(jì)算中間的結(jié)果, 所以變量時(shí)不可變的(無狀態(tài)的)
3. 我們可以吧藝哥函數(shù)的執(zhí)行結(jié)果交給另一個(gè)函數(shù)去處理
// slice? splice
slice 純函數(shù)
let array = [1, 3, 5,6,4]
console.log(array.slice(0, 3))
console.log(array.slice(0, 3))
console.log(array.slice(0, 3))
splice 不純函數(shù)
console.log(array.splice(0, 3))
console.log(array.splice(0, 3))
console.log(array.splice(0, 3))
// 求和純函數(shù)
function getSum (n1, n2){
? ? return n1 + n2
}
console.log(getSum(2, 4))
console.log(getSum(2, 4))
console.log(getSum(2, 4))
4. 純函數(shù)的好處:
? ? (1) 可緩存
? ? (2) 可測(cè)試
? ? (3) 并行處理
function getArea (r){
? ? console.log(r, 'r')
? ? return Math.PI * r * r
}
let getAreaWithMemery = _.memoize(getArea)
console.log(getAreaWithMemery(4))
console.log(getAreaWithMemery(4))
console.log(getAreaWithMemery(4))
console.log(getAreaWithMemery(4))
// 模擬 memoize方法實(shí)現(xiàn)
function memoize (fn){
? ? let cache = {}
? ? return function(){
? ? ? ? console.log(arguments[0], fn)
? ? ? ? let key = JSON.stringify(arguments)
? ? ? ? console.log(key, fn.apply(fn, arguments), 'key')
? ? ? ? cache[key] = cache[key] || fn.apply(fn, arguments)
? ? ? ? return cache[key]
? ? }
}
let getAreaWithMemery = memoize(getArea)
console.log(getAreaWithMemery(4))
console.log(getAreaWithMemery(4))
console.log(getAreaWithMemery(4))
console.log(getAreaWithMemery(4))
#### 柯里化
1. **使用柯里化解決硬編碼的問題**
? ? ***當(dāng)一個(gè)函數(shù)有多個(gè)參數(shù)的時(shí)候先傳遞一部分參數(shù)調(diào)用他(這部分參數(shù)以后永遠(yuǎn)不變)
? ? ***然后返回一個(gè)新的函數(shù)接受剩余的參數(shù)返回結(jié)果
//柯里化演示
function checkAge(age){
? ? let min = 18
? ? return age >= min
}
//普通的純函數(shù)
function checkAge(min, age){
? ? return age >= min
}
console.log(checkAge(18, 20))
console.log(checkAge(18, 20))
console.log(checkAge(22, 20))
console.log(checkAge(24, 20))
// 函數(shù)柯里化
function checkAge(min){
? ? return function(age){
? ? ? ? return age >= min
? ? }
}
// ES6
let checkAge = min => (age => age >= min)
let checkAge18 = checkAge(18)
console.log(checkAge18(20))
console.log(checkAge18(22))
console.log(checkAge18(16))
2. lodash中的柯里化
? _.curry 功能: 創(chuàng)建一個(gè)函數(shù),該函數(shù)接受一個(gè)或多個(gè)func的參數(shù),如果func所需要的參數(shù)都被提供則執(zhí)行func并返回執(zhí)行的結(jié)果. 否則繼續(xù)返回該函數(shù)并等待接收剩余的參數(shù).
function getSum(a, b, c){
? ? return a + b + c
}
const curried = _.curry(getSum)
console.log(curried(2, 3, 5))
console.log(curried(2)(3, 5))
console.log(curried(2)(3)(2))
console.log(curried(2, 3)(2))
//柯里化案例
''.match(/\s+/g)
''.match(/\d+/g)
function match (reg, str){
? ? return str.match(reg)
}
const match = _.curry(function (reg, str){
? ? return str.match(reg)
})
const haveSpace = match(/\s+/g)
const haveNumber = match(/\d+/g)
console.log(haveSpace('hello'))
console.log(haveNumber('123456asldk'))
const filter1 = _.curry(function(fn, array){
? ? return array.filter(fn)
})
console.log(filter1(['ldkk llll', 'liuchao'], haveSpace))
const findSpace = filter1(haveSpace)
console.log(findSpace(['ldkk llll', 'liuchao']))
3. 柯里化實(shí)現(xiàn)原理 模擬實(shí)現(xiàn)lodash.curry方法
function curry(fn){
? ? return function curriedFn(...args){
? ? ? //判斷實(shí)參和行參的個(gè)數(shù)
? ? ? if(args.length < fn.length){
? ? ? ? ? return function(){
? ? ? ? ? ? //? ? return curriedFn(...args.concat([...arguments]))
? ? ? ? ? ? ? return curriedFn(...args.concat(Array.from(arguments)))
? ? ? ? ? }
? ? ? }
? ? ? return fn(...args)
? ? }
}
let curried1 = curry(getSum)
console.log(curried1(1, 2, 3))
console.log(curried1(1)(2)(3))
console.log(curried1(1)(2, 3))
console.log(curried1(1, 2)(3))
4. 函數(shù)柯里化總結(jié)
? ? 1. 柯里化可以讓我們給一個(gè)函數(shù)傳遞較少的參數(shù)得到一個(gè)已經(jīng)記住了某些固定參數(shù)的新函數(shù)
? ? 2. 這是一種對(duì)函數(shù)參數(shù)的‘緩存’
? ? 3. 讓函數(shù)更靈活, 粒度變得更小
? ? 4. 可以把多元函數(shù)轉(zhuǎn)換成一元函數(shù), 可以組合使用函數(shù)產(chǎn)生強(qiáng)大的功能
#### 管道 (自我理解: gulp鏈?zhǔn)綄?shí)現(xiàn))
函數(shù)就是數(shù)據(jù)的管道, 函數(shù)組合就是把這些管道連接起來, 然數(shù)據(jù)穿過多個(gè)管道形成最終結(jié)果
#### 函數(shù)的組合 Compose
1. 純函數(shù)和柯里化很容易寫出洋蔥代碼(f(g(k(x))))
function compose(f, g){
? ? return function(value){
? ? ? ? return f(g(value))
? ? }
}
function reverse (array){
? ? return array.reverse()
}
function first (array){
? ? return array[0]
}
const last = compose(first, reverse)
console.log(last([1, 3, 5, 2]))
2. 函數(shù)組合可以讓我們把細(xì)粒度的函數(shù)重新組合生成一個(gè)新函數(shù)
函數(shù)組合(compose): 如果一個(gè)函數(shù)需要經(jīng)多多個(gè)函數(shù)處理才能得到最終值,這個(gè)時(shí)候可以把中間過程的函數(shù)合并成一個(gè)函數(shù)
**函數(shù)組合默認(rèn)是從右到左執(zhí)行
**函數(shù)組合可以讓代碼最大程度的復(fù)用
// 組合函數(shù)如何調(diào)試
// 實(shí)現(xiàn): NEVER SAY DIE -> never-say-die
const split = _.curry((sep, str) => _.split(str, sep))
const join = _.curry((sep, array) => _.join(array, sep))
const log = v => {
? ? console.log(v)
? ? return v
}
const trace = _.curry((tag, v) => {
? ? console.log(tag, v)
? ? return v
})
const map = _.curry((fn, array) => _.map(array, fn))
// const f = _.flowRight(join('-'), log, map(_.toLower), log, split(' '))
const f = _.flowRight(join('-'), trace('map 后'), map(_.toLower), trace('map 前'), split(' '))
console.log(f('NEVER SAY DIE'))
3. lodash中的組合函數(shù)
// lodash中的組合函數(shù) flow (從左向右執(zhí)行) flowRight (從右向左執(zhí)行)
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = s => s.toUpperCase()
const f = _.flowRight(toUpper, first, reverse)
const f = compose(toUpper, first, reverse)
console.log(f(['aldj', 'kdkdkd']))
// 組合函數(shù)的實(shí)現(xiàn)原理 模擬lodash.flowRight
function compose (...args){
? ? return function(value){
? ? ? ? return args.reverse().reduce((acc, fn) => {
? ? ? ? ? ? return fn(acc)
? ? ? ? }, value)
? ? }
}
// 箭頭函數(shù)實(shí)現(xiàn)
const compose = (...args) => value => args.reverse().reduce((acc, fn) => fn(acc), value)
const f = compose(toUpper, first, reverse)
console.log(f(['aldj', 'kdkdkd']))
4. 組合函數(shù)的實(shí)現(xiàn)原理
結(jié)合律(函數(shù)組合要滿足的特點(diǎn))------我們可以把g和h組合, 還可以把f和g組合, 結(jié)果都是一樣的
const h = _.flowRight(_.toUpper, _.first, _.reverse)
const g = _.flowRight(_.toUpper, _.flowRight(_.first, _.reverse))
const h = _.flowRight(_.flowRight(_.toUpper, _.first), _.reverse)
console.log(h(['aldj', 'kdkdkd']))
console.log(g(['aldj', 'kdkdkd']))
5. lodash中的FP模塊
(1)lodash中的FP模塊提供了實(shí)用的函數(shù)式編程友好的方法
(2)提供了不可變的auto-curried iteratee-first data-last 的方法( 將函數(shù)柯里化 方法前置 數(shù)據(jù)后置)
const fp_f = _fp.flowRight(_fp.join('-'), _fp.map(_fp.toLower), _fp.split(' '))
console.log(fp_f('NEVER SAY DIE'))
//lodash中的FP模塊? 和非FP模塊 map方法的區(qū)別
console.log(_.map(['23', '3', '10'], parseInt))
parseInt('23', 0, array)
parseInt('8', 1, array)
parseInt('10', 2, array)
console.log(_fp.map(parseInt, ['23', '3', '10']))
#### PrintFree 編程風(fēng)格
? ? -- 我們可以把數(shù)據(jù)處理的過程定義成與數(shù)據(jù)無關(guān)的合成運(yùn)算,不需要用到代表數(shù)據(jù)的那個(gè)參數(shù), 只要把簡(jiǎn)單的元素按步驟合成到一起, 在使用這種模式之前我們需要定義一些輔助的基本運(yùn)算函數(shù)
? ? ? ? ** 不需要知名處理的數(shù)據(jù)
? ? ? ? ** 只需要合成運(yùn)算過程
? ? ? ? ** 需要定義一些輔助的基本運(yùn)算函數(shù)
// PrintFree 模式
// Hello? ? World -> hello_world
const f = _fp.flowRight(_fp.replace(/\s+/g, '_'), _fp.toLower)
console.log(f('Hello? ? World'))
// PrintFree 案例
// 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(' '))
console.log(firstLetterToUpper('world wild web'))
#### 函子
#### Functor
1. 容器: 包含值和值的變形(這個(gè)變形關(guān)系就是函數(shù))
2. 函子: 是一個(gè)特殊的容器, 通過一個(gè)普通的對(duì)象來實(shí)現(xiàn), 該對(duì)象具有map方法, map方法可以運(yùn)行一個(gè)函數(shù)對(duì)值進(jìn)行處理(變形關(guān)系)
3. 總結(jié): 1. 函數(shù)式編程的運(yùn)算不直接操作值, 而是由函子完成,
? ? ? ? 2. 函子就是一個(gè)實(shí)現(xiàn)了map契約的對(duì)象
? ? ? ? 3. 我們可以把函子想像成一個(gè)盒子, 這個(gè)盒子里封裝了一個(gè)值
? ? ? ? 4. 行要處理盒子中的值, 我們需要給盒子的方法傳遞藝哥處理值的函數(shù)(純函數(shù)), 有這個(gè)函數(shù)來對(duì)值進(jìn)行處理
? ? ? ? 5. 最終map方法返回一個(gè)包含新值的盒子(函子)
4. 問題: null undefined問題
class Container {
? ? constructor (value){
? ? ? ? this._value = value
? ? }
? ? map(fn){
? ? ? ? return new Container(fn(this._value))
? ? }
}
let r = new Container(5).map(x => x + 1).map(x => x * x)
console.log(r)
class Container {
? ? static of (value){
? ? ? ? return new Container(value)
? ? }
? ? constructor (value){
? ? ? ? this._value = value
? ? }
? ? map(fn){
? ? ? ? return new Container(fn(this._value))
? ? }
}
let r = Container.of(5).map(x => x + 1).map(x => x * x)
console.log(r)
// 問題 (傳入值是null undefined)
Container.of(null).map(x => x.toUpperCase())
#### MayBe函子
? ? MayBe函子的作用就是可以對(duì)外部的空值情況做處理(空值副作用在允許的范圍)
? ? 問題: 多次調(diào)用 不知道哪次調(diào)用出現(xiàn)開始出現(xiàn)null
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(this._value))
? ? }
? ? isNothing(){
? ? ? ? return this._value === null || this._value === undefined
? ? }
}
let r = MayBe.of('Hello kitty').map(x => x.toUpperCase())
console.log(r)
let r = MayBe.of(null).map(x => x.toUpperCase())
console.log(r)
let r = MayBe.of(null).map(x => x.toUpperCase()).map(x => null).map(x => x.split(' '))
console.log(r)
#### 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))
? ? }
}
let r1 = Right.of(12).map(x => x + 2)
let r2 = Left.of(12).map(x => x + 2)
console.log(r1)
console.log(r2)
function parseJSON (str) {
? ? try{
? ? ? ? return Right.of(JSON.parse(str))
? ? } catch(e){
? ? ? ? return Left.of({error: e.message})
? ? }
}
let r = parseJSON('{name: zs}')
console.log(r)
let r = parseJSON('{"name": "zs"}').map(x => x.name.toUpperCase())
console.log(r)
#### IO函子
? ? IO函子的_value是一個(gè)函數(shù),這里是把函數(shù)作為值來處理
? ? IO函子可以把不純的動(dòng)作存儲(chǔ)到_value中, 延遲執(zhí)行這個(gè)不純的操作(惰性執(zhí)行), 包裝但前的操作純
? ? 把不純的操作交給調(diào)用者來處理
? ? 作用: 把傳入函子的不純操作延后, 交給調(diào)用者執(zhí)行, 不再函子中執(zhí)行, 保證函子的純
class IO {
? ? static of(value){
? ? ? ? return new IO(function(){
? ? ? ? ? ? return value
? ? ? ? })
? ? }
? ? constructor(fn){
? ? ? ? this._value = fn
? ? }
? ? map(fn){
? ? ? ? return new IO(_fp.flowRight(fn, this._value))
? ? }
}
let r = IO.of(process).map(v => v.execPath)
console.log(r)
console.log(r._value())
#### IO函子的問題? 多次調(diào)用_value才能拿到目標(biāo)數(shù)據(jù)
// IO函子的問題
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 cat = _fp.flowRight(print, readFile)
//IO(IO(x))
// let r = cat('package.json')
// console.log(r)
// let r = cat('package.json')._value()._value()
// console.log(r)
let r = readFile('package.json')
// .map(x => x.toUpperCase())
.map(_fp.toUpper)
.flatMap(print).join()
console.log(r)
#### Monad函子
? ? Monad函子是可以變扁的函子 IO(IO(x))
? ? 一個(gè)函子如果具有join和of兩個(gè)方法并遵守一些定律就是一個(gè)Monad
class IO {
? ? static of(value){
? ? ? ? return new IO(function(){
? ? ? ? ? ? return value
? ? ? ? })
? ? }
? ? 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()
? ? }
}
#### Pointed函子
? ? Pointed孩子是實(shí)現(xiàn)的of靜態(tài)方法的函子
? ? of方法是為了避免使用new來創(chuàng)建對(duì)象, 更深層的含義是of方法用來把值放到上下文Context(把值放到容器中, 使用map來處理)
#### folktale 不用于lodash ramda的庫, 只提供了一些函數(shù)式操作, 例如: compose、curry等 一些Task、Either、MayBe函子
? ? Task異步執(zhí)行
// folktale庫(2.3.2)
const { compose, curry } = require('folktale/core/lambda')
// let f = curry(2, (x, y) => {
//? ? return x + y
// })
// console.log(f(1, 3))
// console.log(f(1)(3))
// let f = compose(_fp.toUpper, _fp.first)
// console.log(f(['one', 'tow']))
// Task函子 處理異步任務(wù)
// const { task } = require('folktale/concurrency/task')
// function readFile (filename){
//? ? return task( resolver => {
//? ? ? ? fs.readFile(filename, 'utf-8', (err, data) => {
//? ? ? ? ? ? if(err) resolver.reject(err)
//? ? ? ? ? ? resolver.resolve(data)
//? ? ? ? })
//? ? })
// }
// let r = readFile('package.json').map(_fp.split('\n')).map(_fp.find(x => x.includes('version'))).run().listen({
//? ? onRejected: err => {
//? ? ? ? console.log(err)
//? ? },
//? ? onResolved: value => {
//? ? ? ? console.log(value)
//? ? }
// })