Day 19 - Maybe Monad

yo, what's up

在之前我们都是用 Identity 作为例子,但其功用并不大,所以今天要来开始介绍一些比较常用的 ADTs,今天就从 Maybe 开始进行介绍

Maybe 的用途

Maybe Monad 是专门处理无值( null || undefined ) 情境的 Monad

其大部份的使用情境就是安全的取值,举例来说,当我们要透过 url param 的 key 去取 config 值後,再根据娶回来的资料渲染页面

// config.js
const pageConfig = {
    "food": {
        "imageUrl": "https://s3.image.com/food-example.png",
        "foodMenu": [{"title": "fired chicked"}, {"title": "fired rice"}]
    },
    "travel": {
        "imageUrl": "https://s3.image.com/travel-example.png",
        "foodMenu": [{"title": "Taipei"}, {"title": "Tainan"}]
    }
}
// app.js
const { parse } = require('query-string');
const paramsObj = parse(location.search)

此时空值的情况可能发生在 paramsObj 或是 pageConfig[paramsObj.target], 所以我们可能会有很多 if 在程序内

const getPageConfig = (target) => {
    if(!target) return;
    const config = pageConfig[target];
    if(config) {
        ... // do something
    }
}

接下来开始介绍 Maybe 吧!

Maybe 实作

Constructor

Maybe a = Just a | Nothing 

Maybe 可以想像成是一个 Container,里面是由两种情境,有值 Just a 与空值 Nothing 组合而成的

Functor

class Maybe {
  static of(val) {
      return new Maybe(val);
  }
  
  get isNothing() {
      return (this.val === null || this.val === undefined);
  }
  
  constructor(val) {
    this.val = val;
  }

  map(fn) {
    return this.isNothing ? this : Maybe.of(fn(this.val));
  }

  inspect() {
    return this.isNothing ? 'Nothing' : `Just(${this.val})`;
  }
}

Maybe 是一个 Functor, 而其可以与 pure function (unary) 进行 compose

Maybe.of(10)
    .map(R.add(2))
    .inspect() // Just(12)
    
Maybe.of(null)
    .map(R.add(2)) 
    .inspect() // Nothing

可以看到,当 Maybe aa 值为 null 时,就不会继续运算下去,而直接跳过所有运算回传 Nothing

Applicative Functor

class Maybe {
  ...
  ap(f) {
      return this.isNothing ? this : this.map(f)
  }
}

const lift2 = R.curry((g, f1, f2) => f2.ap(f1.map(g)))

而当 pure function 参数长度为 (n-ary),其概念也是跟之前 Identity 的逻辑很像,不同的是 Maybe 一样会判断如果为空值就会回传 Nothing

lift2(R.add, Maybe.of(2), Maybe.of(2)).inspect() // Just(4)

lift2(R.add, Maybe.of(2), Maybe.of(null)).inspect() // Nothing

Chain

class Maybe {
  ...
  join() {
      return this.isNothing ? this : this.val;
  }
  
  chain(f) {
      return this.map(f).join()
  }
}

如果要 compose 的函式是 effect,像是 safeHead 则我们就可以用 chain 去进行 compose

const safeHead = xs => Maybe.of(xs[0])

Maybe.of([1, 2, 3])
    .chain(safeHead)
    .inspect() // Just(1)
    
Maybe.of(null)
    .chain(safeHead)
    .inspect() // Nothing

Option

class Maybe {
  ...
  option(defaultV) {
      return this.isNothing ? defaultV : this.val;
  }
}

到这里大家可能发现一个问题,要如何把 Container 里的值真正取出来,而 Option 的作用就是在此,并且可以放入一个 default value,当取出来的是 Nothing 的时候,Option 会将该值作为 fallback

Maybe.of(100)
    .option('default value') // 100

Maybe.of(null)
    .option('default value') // default value

solution

接下来就来解决我们一开始遇到的问题,在之前可以先 mock parse(location.search) 後的资料,其可能的值为 {target: 'xxx', ...} 或是 {...}

首先如果我们是正向的流程会是这样

const safeGet = R.curry((obj, target) => Maybe.of(obj[target]))

cosnt result = Maybe.of({target: "food"})
    .map(R.prop('target'))
    .chain(safeGet(pageConfig))
    .option({})
    
// {
//   "imageUrl": "https://s3.image.com/food-example.png",
//   "foodMenu": [{"title": "fired chicked"}, {"title": "fired rice"}]
// }

若值为空,则是会 fallback 到 {}

cosnt result = Maybe.of({})
    .map(R.prop('target'))
    .chain(safeGet(pageConfig))
    .option({}) // {}

avaliable on gist

小结

感谢大家阅读,明天将介绍另外一种实作方式

NEXT: Maybe Monad II

Reference

  1. Maybe Moand

<<:  [Day20]如何保障智慧财产?

>>:  Day-20 : devise 安装 part 1

国家标准技术研究院(NIST)最低安全要求的最佳来源-标准

NIST出版物 NIST制定并维护了大量有关信息和信息系统的安全性和隐私性的标准,指南,建议和研究。...

Day8 阿里云架设网站-对象储存

说到物件式储存,可能大家有用过的是AWS的S3、GCP的GCS,OSS (Object Stora...

树状结构转线性纪录-孩子兄弟标记法 - DAY 14

孩子兄弟标记法 记录 右侧索引(右边兄弟是谁),下层所引(孩子是谁) 完整树状转化 参考来源 大话资...

进击的软件工程师之路-软件战斗营 第十五周

学习进度 设计模式 迭代器模式 观察者模式 Android Studio SQLite Room 心...