點燈坊

失くすものさえない今が強くなるチャンスよ

當 Maybe 遇見 Future

Sam Xiao's Avatar 2021-06-02

當有 Function 回傳 Maybe,而後續的 Function 都在 Maybe 內執行時,卻又有 Function 回傳 Future,這就造成 Maybe 內有 Future 窘境,實務上該如何處理這種兩層 Monad 呢 ?

Version

Vue 3.0.11
Sanctuary 3.1.0
Fluture 14.0.0

Future Maybe

future000

當輸入書名找不到時,會顯示 N/A

future001

當找到書名時,就會繼續呼叫 API 查詢價錢。

future002

當 API 回傳 error response 時,則顯示 error message。

<template lang='pug'>
input(v-model='title')
button(@click='onClick') Submit
span {{ price }}
</template>

<script setup>
import { ref } from 'vue'
import { read, write } from 'vue3-fp'
import { pipe, propEq, map, path } from 'ramda'
import { create, env, prop, flip, find } from 'sanctuary'
import { mapRej, fork, resolve } from 'fluture'
import getPrice from '/src/api/getPrice_'

let { maybe, I } = create({ checkTypes: false, env })

let data = [
  { id: 1, title: 'FP in JavaScript' },
  { id: 2, title: 'Elm in Action' },
  { id: 3, title: 'Speaking JavaScript' }
]

let title = ref('')
let price = ref('')

let onClick = pipe(
  read(title),
  propEq('title'),
  flip(find)(data),
  map(prop('id')),
  map(getPrice),
  map(map(path(['data', 'price']))),
  map(mapRej(path(['response', 'data', 'error']))),
  maybe(resolve('N/A'))(I),
  fork(write(price))(write(price))
)
</script>

18 行

let data = [
  { id: 1, title: 'FP in JavaScript' },
  { id: 2, title: 'Elm in Action' },
  { id: 3, title: 'Speaking JavaScript' }
]
  • 使用 find() 查詢 title ,無論是否存在都會回傳 Maybe
  • 後續的處理也都會在 Maybe 內,包含呼叫 API 也會在 Maybe 內
  • API 會回傳 Future,這就造成 Future 在 Maybe 內,也就是 Future Maybe

該如何處理 Future Maybe 這種兩層 Monad 呢 ?

27 行

let onClick = pipe(
  read(title),
  propEq('title'),
  flip(find)(data),
  map(prop('id')),
  map(getPrice),
  map(map(path(['data', 'price']))),
  map(mapRej(path(['response', 'data', 'error']))),
  maybe(resolve('N/A'))(I),
  fork(write(price))(write(price))
)

使用 pipe() 組合 onClick()

  • read(title):讀取 title state
  • propEq('title'):組合 find() 所需要的 callback
  • flip(find)(data):與 Array.prototype.find() 的差異是無論找不找得到都會傳 Maybe,因此不用擔心 undefined 問題
  • map(prop('id'))::在 Maybe 內讀取出 id property
  • map(getPrice):呼叫 getPrice() 從 API 查詢 price 並會回傳 Future
  • map(map(path(['data', 'price']))):關鍵在此行,Future 在 Maybe 內,因此只能 map() 先拆第一層 Maybe,再用 map() 拆第二層 Future,所有的 function 都在 map(map())
  • maybe(resolve('N/A'))(I):處理 Nothing Maybe,將 N/A 包成 Future 回傳
  • fork(write(price))(write(price)):從 Rejected Future 與 Resolved Future 內取出資料寫入 price state

Conclusion

  • Future Maybe 乍聽很嚇人,其實 Maybe 與 Future 都是 Monad,只要把握使用 map()chain() 一層一層處理 Maybe 與 Future,最後先使用 maybe() 處理 Maybe,再使用 fork() 處理 Future 即可