sergeysova/es2-result-option

View on GitHub
index.js

Summary

Maintainability
D
2 days
Test Coverage
/* eslint-disable id-match, no-use-before-define */
const FL = require('fantasy-land')
const show = require('sanctuary-show')


const ø = Symbol('value')
const type = Symbol('type')

function isNullable(value) {
  return value === null || typeof value === 'undefined' || Number.isNaN(value)
}

function Some(value) {
  if (!(this instanceof Some)) {
    return new Some(value)
  }
  this[ø] = value
  this[type] = 'Some'
}

Some.of = (value) => new Some(value)

Object.assign(Some.prototype, {
  map(ƒ) {
    return Some.of(ƒ(this[ø]))
  },

  ap(option) {
    return option.chain((ƒ) => this.map(ƒ))
  },

  chain(ƒ) {
    return ƒ(this[ø])
  },

  equals(option) {
    return option.map((value) => value === this[ø]).extractOr(false)
  },

  filter(ƒ) {
    return this.chain((value) => ƒ(value) ? Some.of(value) : Option.zero())
  },

  alt(/* option */) {
    return this
  },

  extend(ƒ) {
    return Some.of(ƒ(this))
  },


  mapOr(value, ƒ) {
    return this.map(ƒ)
  },

  mapOrElse(defaultƒ, mapƒ) {
    return this.map(mapƒ)
  },

  exists(ƒ) {
    return Boolean(ƒ(this[ø]))
  },

  or(/* option */) {
    return this
  },

  orElse(/* optionƒ */) {
    return this
  },

  and(option) {
    return option
  },

  andThen(optionƒ) {
    return optionƒ()
  },

  extractOr(/* defaultValue */) {
    return this[ø]
  },

  extractOrElse(/* defaultƒ */) {
    return this[ø]
  },

  * iter() {
    yield this[ø]
  },

  isSome() {
    return true
  },

  isNone() {
    return false
  },

  okOr(/* err */) {
    return Ok.of(this[ø])
  },

  okOrElse(/* errƒ */) {
    return Ok.of(this[ø])
  },

  '@@show': function __show() {
    return `Some(${show(this[ø])})`
  },

  of: Some.of,

  toString() {
    return `Some(${this[ø]})`
  },

  inspect() {
    return this.toString()
  },
})

const nothing = Symbol('None')
const noneConst = new None()

function None() {
  if (!(this instanceof None)) {
    return noneConst
  }
  this[ø] = nothing
  this[type] = 'None'
}

None.of = () => noneConst

Object.assign(None.prototype, {

  map(/* ƒ */) {
    return None.of()
  },

  ap(/* option */) {
    return None.of()
  },

  chain(/* ƒ */) {
    return None.of()
  },

  equals(option) {
    return option.map(() => false).extractOr(true)
  },

  filter(/* ƒ */) {
    return None.of()
  },

  alt(option) {
    return option
  },

  extend(/* ƒ */) {
    return None.of()
  },


  mapOr(value /* ƒ */) {
    return Some.of(value)
  },

  mapOrElse(defaultƒ /* mapƒ */) {
    return Some.of(defaultƒ())
  },

  exists(/* ƒ */) {
    return false
  },

  or(option) {
    return option
  },

  orElse(optionƒ) {
    return optionƒ()
  },

  and(/* option */) {
    return this
  },

  andThen(/* optionƒ */) {
    return this
  },

  extractOr(defaultValue) {
    return defaultValue
  },

  extractOrElse(defaultƒ) {
    return defaultƒ()
  },

  * iter() {
    // nothing yields
  },

  isSome() {
    return false
  },

  isNone() {
    return true
  },

  okOr(errValue) {
    return Err.of(errValue)
  },

  okOrElse(errƒ) {
    return Err.of(errƒ())
  },

  '@@show': function __show() {
    return 'None()'
  },

  toString() {
    return 'None()'
  },

  inspect() {
    return this.toString()
  },

  of: None.of,
})

function option$is(value) {
  return value instanceof Some || value instanceof None
}

function option$into(value) {
  return option$is(value)
    ? value
    : Some.of(value)
}

function option$encase(ƒ) {
  return function encased(...args) {
    try {
      return Some.of(ƒ.apply(this, args))
    }
    catch (error) {
      return None.of()
    }
  }
}

function option$fromNullable(value) {
  return isNullable(value)
    ? None.of()
    : Some.of(value)
}

const Option = {
  encase: option$encase,
  fromNullable: option$fromNullable,
  into: option$into,
  is: option$is,
  none: None.of,
  of: Some.of,
  some: Some.of,
  zero: None.of,
}

Some.prototype[FL.equals] = Some.prototype.equals
Some.prototype[FL.map] = Some.prototype.map
Some.prototype[FL.ap] = Some.prototype.ap
Some.prototype[FL.alt] = Some.prototype.or
Some.prototype[FL.chain] = Some.prototype.chain
Some.prototype[FL.extend] = Some.prototype.extend
Some[FL.of] = Some.of
Some[FL.zero] = Option.zero

None.prototype[FL.equals] = None.prototype.equals
None.prototype[FL.map] = None.prototype.map
None.prototype[FL.ap] = None.prototype.ap
None.prototype[FL.alt] = None.prototype.or
None.prototype[FL.chain] = None.prototype.chain
None.prototype[FL.extend] = None.prototype.extend
None[FL.of] = None.of
None[FL.zero] = Option.zero

function Ok(value) {
  if (!(this instanceof Ok)) {
    return new Ok(value)
  }
  this[ø] = value
  this[type] = 'Ok'
}

Ok.of = (value) => new Ok(value)

Ok.prototype = {

  map(okƒ) {
    return Ok.of(okƒ(this[ø]))
  },

  mapOr(value, okƒ) {
    return this.map(okƒ)
  },

  mapOrElse(defaultƒ, mapƒ) {
    return this.map(mapƒ)
  },

  mapErr(/* errƒ */) {
    return this
  },

  mapErrOr(value /* errƒ */) {
    return Err.of(value)
  },

  mapErrOrElse(defaultƒ /* errƒ */) {
    return Err.of(defaultƒ())
  },

  chain(okƒ) {
    return okƒ(this[ø])
  },

  chainErr(/* æƒ */) {
    return this
  },

  ap(result) {
    return result.chain((ƒ) => this.map(ƒ))
  },

  apErr(/* result */) {
    return this
  },

  equals(result) {
    return result.map((value) => value === this[ø]).extractOr(false)
  },

  extractOr(/* defaultValue */) {
    return this[ø]
  },

  extractOrElse(/* defaultƒ */) {
    return this[ø]
  },

  extractErrOr(defaultValue) {
    return defaultValue
  },

  extractErrOrElse(defaultƒ) {
    return defaultƒ()
  },

  * iter() {
    yield this[ø]
  },

  * iterErr() {
    yield this[ø]
  },

  swap() {
    return Err.of(this[ø])
  },

  promise() {
    return Promise.resolve(this[ø])
  },

  either(okƒ /* errƒ */) {
    return okƒ(this[ø])
  },

  bimap(okƒ /* errƒ */) {
    return this.map(okƒ)
  },

  isOk() {
    return true
  },

  isErr() {
    return false
  },

  ok() {
    return Some.of(this[ø])
  },

  err() {
    return None.of()
  },
}

function Err(value) {
  if (!(this instanceof Err)) {
    return new Err(value)
  }
  this[ø] = value
  this[type] = 'Err'
}

Err.of = (value) => new Err(value)

Err.prototype = {

  map(/* okƒ */) {
    return this
  },

  mapOr(value /* okƒ */) {
    return Ok.of(value)
  },

  mapOrElse(defaultƒ /* okƒ */) {
    return Ok.of(defaultƒ())
  },

  mapErr(errƒ) {
    return Err.of(errƒ(this[ø]))
  },

  mapErrOr(value, errƒ) {
    return this.mapErr(errƒ)
  },

  mapErrOrElse(defaultƒ, errƒ) {
    return this.mapErr(errƒ)
  },

  chain(/* okƒ */) {
    return this
  },

  chainErr(errƒ) {
    return errƒ(this[ø])
  },

  ap(/* result */) {
    return this
  },

  apErr(result) {
    return result.chain((ƒ) => this.mapErr(ƒ))
  },

  equals(result) {
    return result.mapErr((value) => value === this[ø]).extractErrOr(false)
  },

  extractOr(defaultValue) {
    return defaultValue
  },

  extractOrElse(defaultƒ) {
    return defaultƒ()
  },

  extractErrOr(/* defaultValue */) {
    return this[ø]
  },

  extractErrOrElse(/* defaultƒ */) {
    return this[ø]
  },

  * iter() {
    // nothing yields
  },

  * iterErr() {
    yield this[ø]
  },

  swap() {
    return Ok.of(this[ø])
  },

  promise() {
    return Promise.reject(this[ø])
  },

  either(okƒ, errƒ) {
    return errƒ(this[ø])
  },

  bimap(okƒ, errƒ) {
    return this.mapErr(errƒ)
  },

  isOk() {
    return false
  },

  isErr() {
    return true
  },

  ok() {
    return None.of()
  },

  err() {
    return Some.of(this[ø])
  },
}

function result$is(value) {
  return value instanceof Ok || value instanceof Err
}

function result$encase(ƒ) {
  return function encased(...args) {
    try {
      return Ok.of(ƒ.apply(this, args))
    }
    catch (error) {
      return Err.of(error)
    }
  }
}

function result$into(value) {
  return result$is(value)
    ? value
    : Ok.of(value)
}

const Result = {
  ok: Ok.of,
  err: Err.of,
  is: result$is,
  into: result$into,
  encase: result$encase,
}

module.exports = {
  Some,
  None,
  Option,

  Ok,
  Err,
  Result,
}