safareli/free

View on GitHub
src/par.js

Summary

Maintainability
A
3 hrs
Test Coverage
const daggy = require('daggy')
const { of, ap } = require('sanctuary-type-classes')
const patch = require('./fl-patch')
const { id, compose } = require('./utils')

// data Par f a where
//  Pure :: a -> Par f a
//  Lift :: f a -> Par f a
//  Ap :: Par f (a -> b) -> Par f a -> Par f b
const Par = daggy.taggedSum('Par', {
  Pure: ['x'],
  Lift: ['i'],
  Ap: ['f', 'x'],
})
const { Pure, Lift, Ap } = Par

Object.assign(Par, patch({
  // :: a -> Par f a
  of: Pure,
  // :: f a -> Par f a
  lift: Lift,
}))

Object.assign(Par.prototype, patch({
  // :: Par f a ~> (a -> b) -> Par f b
  map(f) {
    return ap(Par.of(f), this)
  },
  // :: Par f a ~> Par f (a -> b) -> Par f b
  ap(that) {
    return Ap(that, this)
  },
  // :: (Applicative g) => Par f a ~> (Ɐ x. f x -> g x, TypeRep g) -> g a
  foldPar(f, T) {
    const argsF = [this]
    const fns = []
    while (true) {
      let argF = argsF.pop()

      if (Ap.is(argF)) {
        const lengthInitial = argsF.length
        while (Ap.is(argF)) {
          argsF.push(argF.x)
          argF = argF.f
        }
        fns.push(Fn(foldArg(argF, f, T), argsF.length - lengthInitial))
        continue
      }

      const argT = foldArg(argF, f, T)
      if (fns.length === 0) {
        return argT
      }
      let fn = fns.pop()
      let res = ap(fn.f, argT)

      if (fn.length > 1) {
        fns.push(Fn(res, fn.length - 1))
        continue
      }

      while (fns.length > 0) {
        fn = fns.pop()
        res = ap(fn.f, res)
        if (fn.length > 1) {
          fns.push(Fn(res, fn.length - 1))
          break
        }
      }

      if (fns.length === 0) {
        return res
      }
    }
  },
  // :: Par f a ~> (Ɐ x. f x -> g x) -> Par g a
  hoistPar(f) {
    return this.foldPar(compose(Par.lift)(f), Par)
  },
  // :: (Applicative f) => Par f a ~> TypeRep f -> f a
  retractPar(m) {
    return this.foldPar(id, m)
  },
  // :: Par f a ~> (Ɐ x. f x -> Par g x) -> Par g a
  graftPar(f) {
    return this.foldPar(f, Par)
  },
}))

// Internal helper function for foldPar it folds only Pure and Lift nodes
const foldArg = (node, f, T) => {
  if (Pure.is(node)) {
    return of(T, node.x)
  } else if (Lift.is(node)) {
    return f(node.i)
  }
}

// Internal helper structure for foldPar it conatins an Applicative containing
// a function and information on how many argument it needs
// type Fn g a b = { fun:: g (a -> b), length:: Number}
const Fn = (f, length) => ({ f, length })

module.exports = Par