mezzanine

0.2.1 • Public • Published

Mezzanine

npm versionbuild status

Fantasy land union types with pattern matching

Installation

$ npm install --save mezzanine

Motivation

Principles:

  • No this
  • No new
  • No .prototype
  • Minimal api surface

Object classes, described with rules and linked methods.

Usage

import { Type, Union } from 'mezzanine'
 
const Point = Type`Point`({
  x: Number,
  y: Number,
})
 
const Shape = Union`Shape`({
  Line: {
    start: Point,
    end  : Point,
  },
  Circle: {
    center: Point,
    radius: Number,
  },
})
const point1 = Point({ x: 1, y: 2 })
const point2 = Point({ x: 0, y: 10 })
 
point1.equals(Point({ x: 1, y: 2 })) // => true
point1.equals({ x: 1, y: 2 }) // => true, smart type inference
 
const shape1 = Shape({
  start: { x: 0, y: 0 },
  end  : point2,
})
shape1.type // => Line
 

syntaxShockMode = off

You can also use the library without backtick tags, with classic parentheses.

import { Type } from 'mezzanine'
const Point = Type('Point')({ x: Number, y: Number })

Methods & computed properties

Second argument in the constructor is the function map, object with linked methods, which apply and attach to instances on create

import { Type } from 'mezzanine'
 
const Point = Type`Point`({
  x: Number,
  y: Number,
}, {
  concat:
    (ctx) => // context, works as `this`
      (point) => // can be another Point instance or plain object
        Point({
          x: ctx.x + point.x,
          y: ctx.y + point.y
        })
})
 
const Rectangle = Type`Rectangle`({
  root  : Point,
  width : Number,
  height: Number
}, {
  area: ctx => ctx.width * ctx.height, //Will be computed property
  endPoint(ctx) {
    return ctx.root.concat({ // Use Point .concat method
      x: ctx.width,
      y: ctx.height
    })
  }
})
 
const rect = Rectangle({
  root: { x: 1, y: 4 },
  width: 10,
  height: 5
})
/*
  => {
    type    : 'Rectangle',
    root    : { type: 'Point', x: 1, y: 4 },
    width   : 10,
    height  : 5,
    area    : 50,
    endPoint: { type: 'Point', x: 11, y: 9 }
  }
*/
 

Iterable types

You can define iterable types with Symbol.iterator. Another symbols, well-known or not, are also available as method names

const Iterable = Type`Iterable`(Array, {
  length: ({ value }) => value.length,
  [Symbol.iterator](ctx) {
    return function* () {
      const length = ctx.length
      for (let i = 0; i < length; i++)
        yield (ctx.value[i])
    }
  },
})
const result = [...Iterable(['a', 'b', 'c'])]
// => ['a', 'b', 'c']

Data types

Mezzanine has some built-in object classes.

Tuple

import { Tuple } from 'mezzanine'
 
const Point = Type`Point`({ x: Number, y: Number })
 
const NamedPoint = Tuple(String, Point)
 
const point1 = NamedPoint('start point', Point({ x: 1, y: 1 }))
const point2 = NamedPoint('end point', { x: 2, y: 3 })
 
NamedPoint.is(['label', { x: 0, y: 0 }]) // => true

Tuples are iterable

point2.length
// => 2
for (const value of point2) {
  console.log(value)
}
// => 'end point'
// => { type: 'Point', x: 2, y: 3 }

Maybe

import { Maybe } from 'mezzanine'
 
const filterFarmer = human => human.occupation === 'farmer'
 
const users = {
  230: { name: 'bob', occupation: 'farmer' },
  231: { name: 'jerry', occupation: 'doctor' },
  232: { name: 'frank', occupation: 'teacher' }
}
 
const readField = prop => data => data[prop]
 
const toUpperCase = (text) => text.toUpperCase()
 
const maybeName =
  Maybe(users)
    .map(readField(230))
    .filter(filterFarmer)
    .map(readField('name'))
    .map(toUpperCase)
    .toJSON()
// => BOB

What happens if we select an id that doesn't exist? Nothing. The whole chain will safely skip incorrect values without changes

Ramda support

import { pipe, map, filter, chain } from 'ramda'
import { Maybe } from 'mezzanine'
 
const readName = (id) => pipe(
  Maybe,
  map(readField(id)),
  filter(filterFarmer),
  map(readField('name')),
  map(toUpperCase)
)
 
readName(230)(users) // => { type: 'Just', value: 'BOB' }
readName(231)(users) // => { type: 'Nothing', value: undefined }
readName(NaN)(users) // => { type: 'Nothing', value: undefined }
 

Recipes

Any type

import { T } from 'ramda'
 
const Any = Type`Any`(T)

or

const Any = Type`Any`(() => true)

Usage:

const example1 = Any('ok')
const example2 = Any(null)
const example3 = Any()

License

The project is released under the Mit License

Package Sidebar

Install

npm i mezzanine

Weekly Downloads

1

Version

0.2.1

License

MIT

Last publish

Collaborators

  • drelliot