JavaScript函數式編程之函子

来源:https://www.cnblogs.com/fengbaba/archive/2022/10/23/16819958.html
-Advertisement-
Play Games

函子(Functor) 函子是一個特殊的容器,通過一個普通對象來實現,該對象具有map方法,map方法可以運行一個函數對值進行處理(變形關係),容器包含值和值變形關係(這個變形關係就是函數)。函數式編程中解決副作用的存在 函數式編程的運算不直接操作值,,而是由函子完成 函子就是一個實現了map契約的 ...


函子(Functor)

函子是一個特殊的容器,通過一個普通對象來實現,該對象具有map方法,map方法可以運行一個函數對值進行處理(變形關係),容器包含值和值變形關係(這個變形關係就是函數)。函數式編程中解決副作用的存在

  • 函數式編程的運算不直接操作值,,而是由函子完成
  • 函子就是一個實現了map契約的對象
  • 我們可以把函子想象成一個盒子,盒子裡面封裝了一個值
  • 想要處理盒子中的值,我們需要給盒子的map方法傳遞一個處理值的函數(純函數),由這個函數來對值進行處理
  • 最終map方法返回一個包含新值所在的盒子(函子)

根據函子的定義我們創建一個函子

// functor 函子
class Container {
  constructor (value) {
    // 函子內部保存這個值。下劃線是不想外部訪問
    this._value = value
  }

  // map 方法接收一個處理值的函數
  map (fn) {
    return new Container(fn(this._value))
  }
}

此時就已經創建了一個函子但是這是面向對象的方式來創建的,換成用函數式編程來寫一個函子

class Container {
  constructor (value) {
    this._value = value
  }

  map (fn) {
    return Container.of(fn(this._value))
  }

  static of (value) {
    return new Container(value)
  }
}

let x = Container.of(5).map(x => x + 1).map(x => x - 1)

但是這個函子還是存在一些問題,比如空值的時候就會報錯, 會讓我們的函子變的不純,我們需要去攔截空值錯誤,我們創建一個方法去判斷是否為空值,如果是控制我們直接返回一個空值的函子,如果有值再去處理,這個時候就需要使用MayBe函子

let x = Container.of(null).map(x => x + 1).map(x => x - 1)

MayBe 函子

我們在編程的過程中可能會遇到很多錯誤,需要對這些錯誤做相應的處理,MayBe函子的作用就是可以對外部的空值情況做處理(控制副作用在允許的範圍)

// MayBe 函子
class MayBe {
  constructor (value) {
    this._value = value
  }

  map (fn) {
    return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
  }

  isNothing () {
    return this._value === undefined || this._value === null
  }

  static of (value) {
    return new MayBe(value)
  }
}

let x = MayBe.of(null)
  .map(x => x + 1)
  .map(x => x - 1)
console.log(x)

這個時候我們已經能正常執行了,但是現在出現了空值的函子,但是我們不知道那個地方出現了空值,所以我們創建兩個函子一個是正常的處理一個是出現錯誤情況處理,正常的就按照正常的方式創建,錯誤的是是否我們把map方法改造一下讓她不再處理回調函數,直接返回一個空值的MayBe函子,這樣就記錄下了錯誤信息Eitcher 函子就是來處理這種情況的

Either函子

Eitcher 類似於 if else 的處理,兩者中的任何一個,異常會讓函數變的不純,Eitcher函子可以用來做異常處理

// 因為是二選一,所以定義兩個類 Left 和 Right

// 記錄錯誤信息的
class Left {
  constructor (value) {
    this._value = value
  }

  map (fn) {
    return this
  }

  static of (value) {
    return new Left(value)
  }
}

// 正常處理
class Rgiht {
  constructor (value) {
    this._value = value
  }

  map (fn) {
    return Rgiht.of(fn(this._value))
  }

  static of (value) {
    return new Rgiht(value)
  }
}

function parseJson (str) {
  try {
    return Rgiht.of(JSON.parse(str))
  } catch (err) {
    return Left.of({ message: err.message })
  }
}

// 故意傳入錯誤的數據
let r = parseJson('{ name: "2" }')
r.map(x => x.name.toUpperCase())
console.log(r)

IO 函子

IO 函子中的 _value 是一個函數, 這裡把函數作為值來處理, IO 函子可以吧不純的動作儲存到_value中,延遲這個不純的操作(惰性執行),保證當前的操作是純的,延遲把不純的操作到調用者來處理

const fp = require('lodash/fp')

// IO 函子
class IO {
  constructor (fn) {
    this._value = fn
  }
  static of (value) {
    return new IO(function () {
      return value
    })
  }
  map (fn) {
    // 把當前的value 和傳入的fn 函數組合成一個新的函數
    return new IO(fp.flowRight(fn, this._value))
  }
}

let r = IO.of(process).map(x => x.execPath)

console.log(r)
console.log(r._value())

IO 函子內部幫我們包裝了一些函數,當我們傳遞函數的時候有可能這個函數是一個不純的操作,不管這個函數純與不純,IO這個函子在執行的過程中它返回的這個結果始終是一個純的操作,我們調用map的時候始終返回的是一個函子,但是IO函子這個_value屬性他裡面要去合併很多函數,所以他裡面可能是不純的,把這些不純的操作延遲到了調用的時候,也就是我們通過IO函子控制了副作用的在可控的範圍內發生

實現 liunx 下 cat 命令

const fp = require('lodash/fp')

// IO 函子
class IO {
  constructor (fn) {
    this._value = fn
  }
  static of (value) {
    return new IO(function () {
      return value
    })
  }
  map (fn) {
    // 把當前的value 和傳入的fn 函數組合成一個新的函數
    return new IO(fp.flowRight(fn, this._value))
  }
}

let r = IO.of(process).map(x => x.execPath)

function readFile (fileName) {
  return new IO(() => fs.readFileSync(fileName, 'utf-8'))
}

function print (x) {
  return new IO(() => {
    console.log(x)
    return x
  })
}

let cat = fp.flowRight(print, readFile)

console.log(cat('package.json')._value()._value())

此時IO函子出現了嵌套的問題,導致調用嵌套函子中的方法就必須要要._value()._value() 這樣來執了,嵌套了幾層就需要幾層調用

Folktale

Folktale 是一個標準的函數式編程庫,和lodash不同的是,他沒有提供很多功能函數,只提供了一些函數式處理的操作,例如:compose、curry等,一些函子 Task、Either、MayBe等,

Folktale 中的currycompose的簡單使用

const { compose, curry } = require('folktale/core/lambda')
const { toUpper, first } = require('lodash/fp')

// 與lodash區別,第一個參數指明後面參數的個數
let f = curry(2, (n1, n2) => n1 + n2)

console.log(f(1, 2))

// compose 就是函數組合 lodash 中的函數組合是 flowRight
let f2 = compose(toUpper, first)

console.log(f2(['one', 'two']))

Folktale 中的 task 函子

函子可以處理非同步任務,在非同步任務中會通往地獄之門的回調,而使用task 函子可以避免回調的嵌套,詳細請看官方文檔

// Task 非同步任務
const { task } = require('folktale/concurrency/task')
const { split, find } = require('lodash/fp')
const fs = require('fs')

function readFile (filename) {
  return task(resolver => {
    fs.readFile(filename, 'utf-8', (err, data) => {
      if (err) {
        resolver.reject(err)
      }
      resolver.resolve(data)
    })
  })
}

readFile('package.json')
  .map(split('\n'))
  .map(find(x => x.includes('version')))
  // 執行讀取文件
  .run()
  .listen({
    onRejected(err) {
      console.log(err)
    },
    onResolved(value) {
      console.log(value)
    }
  })

Pointed函子

Pointed函子 是實現了of靜態方法, of 方法是為了避免使用new 來創建對象,更深層次含義是of方法把值放到上下文Context(把值放到容器中,使用map 來處理值)

class Container {
  constructor (value) {
    this._value = value
  }
	static of () {
    return new Container(value)
  }
  map (fn) {
    return new Container(fn(this._value))
  }
}

Monad函子

解決函子嵌套的問題,Monad 函子是可以變扁的 Pointed 函子 IO(IO),一個函子如果具有joinof兩個方法並遵循一些定律就是一個Monad

class IO {
  constructor (fn) {
    this._value = fn
  }
  static of (value) {
    return new IO(function () {
      return value
    })
  }
  map (fn) {
    return new IO(fp.flowRight(fn, this._value))
  }

  join () {
    return this._value()
  }

  // 同時調用 join 和 map
  flatMap (fn) {
    return this.map(fn).join()
  }
}

function readFile (fileName) {
  return new IO(() => fs.readFileSync(fileName, 'utf-8'))
}

function print (x) {
  return new IO(() => {
    return x
  })
}

let r = readFile('package.json').flatMap(print).join()

console.log(r)

當我們想要去調用一個方法,這個方法返回一值的時候我們去調用map方法,當我們想要去調用一個方法,這個方法返回一個函子的時候我們去調用flatMap方法

原文地址:https://kspf.xyz/archives/17
更多內容微信公眾號搜索充饑的泡飯
小程式搜一搜開水泡飯的博客


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • topaz clean 3 Mac版是款簡單實用的去處圖像噪點的濾鏡插件;它擁有非常獨特的演算法,可以支持用戶快速的去除大面積或者不相同種類靜態圖片還是那個面的噪點,支持進行細節圖像的保留;還擁有可以將照片快速的變成手會風格的圖片,使您的圖片更加的具有真實性。 詳情:Topaz Clean 3 for ...
  • Bomb Lab 引言:主要任務是“拆炸彈”。所謂炸彈,其實就是一個二進位的可執行文件,要求輸入六個字元串,每個字元串對應一個phase。如果字元串輸入錯誤,系統就會提示BOOM!!!解決這次實驗需要將二進位文件反彙編,通過觀察理解彙編語言描述的程式行為來猜測符合條件的字元串。可以看出該可執行程式要 ...
  • 前言:在zookeeper學習的時候,執行jsp命令查看zookpper運行狀態的時候發現報錯: -bash: jps: command not found 翻閱了一大批文章,不是東拼西湊,就是缺斤少兩,於是乎,本人萌生了第一次寫博客的想法,復盤的同時,順便記錄一下此次踩坑的經過,開始吧,GOGOG ...
  • 創建/更新存儲過程 基礎基礎用法 創建/修改無參存儲過程 CREATE OR REPLACE PROCEDURE procedure_name [IS|AS] --聲明全局變數(可選) BEGIN --存儲過程的執行體 END; --也可以寫成 END procedure_name; 創建/修改攜參 ...
  • 1.創建表 char 和 varchar 如何選擇? char 適用於數長度不會發生改變的時候,是定長的,例如:性別,生日varchar 當一個欄位數據長度不確定,例如:簡介、姓名、等都是採用varcharchar是直接開闢一定長度的空間,varchar是根據存儲數據的長度動態的開闢空間! 測試案例 ...
  • Mac上哪款PostgreSQL資料庫管理工具好用?PostgreSQL Mac版推薦給大家,它是一個現代化的PostgreSQL客戶端,提供了一個易於使用的界面,使Postgres更容易為新手和專家所操作。 詳情:Postico for Mac(PostgreSQL資料庫管理工具) Postico ...
  • 本文將介紹三種資料庫變慢場景的分析與優化方法. 1、已經定位出的特定慢SQL 2、整個資料庫實例(幾乎所有SQL)變慢, 或者某些時候整個資料庫實例大面積SQL變慢(大面積抖動) 3、某些正常情況下很快的SQL偶爾會變慢(抖動) ...
  • HTML (HyperText Markup Language,超文本標記語言) 不是一門編程語言,而是一種用於定義內容結構的標記語言,用來描述網頁內容,文件格式為.html。HTML 由一系列的元素(elements)組成,這些元素用來實現不同的內容。HTML5是HTML新的修訂版本,2014年由... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...