Skip to content

zerobias/mezzanine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

About

Fantasy land union types with pattern matching

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published