• This repository has been archived on 29/Nov/2018
  • Stars
    star
    696
  • Rank 64,708 (Top 2 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created almost 11 years ago
  • Updated almost 10 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Native pattern matching for JavaScript

sparkler

Sparkler is a pattern matching engine for JavaScript built using sweet.js macros, so it looks and feels like native syntax. It has no runtime dependencies and compiles down to simple ifs and fors.

Here's a small slice of what you can do with it:

function myPatterns {
  // Match literals
  42 => 'The meaning of life',

  // Tag checking for JS types using Object::toString
  a @ String => 'Hello ' + a,

  // Array destructuring
  [...front, back] => back.concat(front),

  // Object destructuring
  { foo: 'bar', x, 'y' } => x,

  // Custom extractors
  Email { user, domain: 'foo.com' } => user,

  // Rest arguments
  (a, b, ...rest) => rest,

  // Rest patterns (mapping a pattern over many values)
  [...{ x, y }] => _.zip(x, y),

  // Guards
  x @ Number if x > 10 => x
}

You can see a slew of more examples in the pattern spec file

Install

npm install -g sweet.js
npm install sparkler
sjs -m sparkler/macros myfile.js

How Does It Work?

Sparkler overloads the function keyword as a macro (don't worry, all your old functions will still work) but implements a slightly different syntax. There's no argument list after the name or function keyword. Instead the function body is just a set of ES6 style arrow-lambdas separated by commas.

function myFunc {
  // Single arguments don't need parens and simple expression 
  // bodies get an implicit `return`.
  a @ String => 'Hello ' + a,

  // Add parens and braces if you need more.
  (x, y, z) => {
    return x + y + z;
  }
}

You can also do this with anonymous functions:

DB.getResource('123', function {
  (null, resp) => complete(resp),
  (err) => handleError(err)
})

If no case matches, a TypeError('No match') is thrown.

Optimization

Sparkler doesn't just try each case one at a time until one passes. That would be really inefficient. Instead, it analyzes your entire pattern matrix, and rearranges things as needed to get an optimized set of tests while still preserving the left-to-right, top-down semantics.

function expensiveExtraction {
  (MyExtractor(x, y, z), 1) => doThis(),
  (MyExtractor(x, y, z), *) => doThat()
}

Let's say MyExtractor is really expensive. Sparkler efficiently backtracks, so it will only get called once in this set of tests.

Argument Length

In JavaScript, you can call a function with any number of arguments. Arguments that are not provided are just set to undefined. Sparkler does not implicitly match on argument length.

function ambiguous {
  (a)       => 1,
  (a, b)    => 2,
  (a, b, c) => 3
}

The above function will always return 1 no matter how many arguments you call it with as the first case always matches. The subsequent cases are actually removed from the final output in the optimization phase.

If you want to match on specific argument length, you need to add a guard to your case.

function argCheck {
  // Using arguments.length
  (a, b, c) if arguments.length == 3 => 1,
  // Or matching undefined
  (a, b, undefined) => 2,
  (a) => 1
}

The only time Sparkler is strict with argument length is with the empty parameter list (). It will check that arguments.length is zero. This is so you can do stuff like this:

Foo.prototype = {
  // jQuery-style getter/setter
  val: function {
    ()  => this._val,
    val => {
      this._val = val;
      return this;
    }
  }
}

If you want a catch-all, you should use a wildcard (*) or default instead.

Match Keyword

Sparkler exports a match macro for doing easy matching in any position.

Match Expressions

Match expressions look like function matching, except you provide the argument(s) upfront.

var num = 12;
var isNumber = match num {
  Number => true,
  *      => false
};

This works by desugaring match into a self-invoking function with num as the argument. Consequently, match expressions do not support break, continue, and early return. Using a match expression in statement position will result in a parse error.

Match Statements

Match statements use a slightly different syntax. They look like a suped up switch.

var a = Foo(Foo(Foo(42)));
while (1) {
  match a {
    case Foo(inner):
      a = inner;
    default:
      break;
  }
}

Unlike switches, cases in a match statement do not fall through. Early return, break, and switch are all supported. Using a match statement in expression position will result in a parse error.

Multiple Matches

You can match on multiple expressions at once in both match expressions and statements.

var allNums = match (num1, num2, num3) {
  (Number, Number, Number) => true,
  *                        => false
};

match (num1, num2, num3) {
  case (Number, Number, Number):
    allNums = true;
  default:
    allNums = false;
}

Pattern Bindings and Hoisting

All bindings in patterns are declared as vars by default, as it is the most widely supported declaration form. Consequently, they will hoist outside of match statements. You may specify your declaration form by prefixing pattern bindings with one of var, let, or const.

match x {
  case Foo(a):       ... // will hoist
  case Foo(var a):   ... // will hoist
  case Foo(let a):   ... // will not hoist
  case Foo(const a): ... // will not hoist, immutable
}

Custom Extractors

You can match on your own types by implementing a simple protocol. Let's build a simple extractor that parses emails from strings:

var Email = {
  // Factor out a matching function that we'll reuse.
  match: function {
    x @ String => x.match(/(.+)@(.+)/),
    *          => null
  },

  // `hasInstance` is called on bare extractors.
  hasInstance: function(x) {
    return !!Email.match(x);
  },

  // `unapply` is called for array-like destructuring.
  unapply: function(x) {
    var m = Email.match(x);
    if (m) {
      return [m[1], m[2]];
    }
  },

  // `unapplyObject` is for object-like destructuring.
  unapplyObject: function(x) {
    var m = Email.match(x);
    if (m) {
      return {
        user: m[1],
        domain: m[2]
      };
    }
  }
};

Now we can use it in case arguments:

function doStuffWithEmails {
  // Calls `unapplyObject`
  Email { domain: 'foo.com' } => ...,
  
  // Calls 'unapply'
  Email('foo', *) => ...,

  // Calls `hasInstance`
  Email => ...
}

If you don't implement hasInstance, Sparkler will fall back to a simple instanceof check.

adt-simple

adt-simple is a library that implements the extractor protocol out of the box, and even has its own set of macros for defining data-types.

var adt = require('adt-simple');
union Tree {
  Empty,
  Node {
    value : *,
    left  : Tree,
    right : Tree
  }
} deriving (adt.Extractor)

function treeFn {
  Empty => 'empty',
  Node { value @ String } => 'string'
}

Optional Extensions

Sparkler also provides a small library that extends some of the native types with convenient functions.

require('sparkler/extend');

// Date destructuring
function dateStuff {
  Date { month, year } => ...
}

// RegExp destructuring
function regexpStuff {
  RegExp { flags: { 'i' }} => ...
}

// Partial-function composition with `orElse`
function partial {
  Foo => 'foo'
}

var total = partial.orElse(function {
  * => 'anything'
})

orElse is added to the prototype safely using defineProperty and isn't enumerable.


Author

Nathan Faubion (@natefaubion)

License

MIT

More Repositories

1

matches.js

Powerful pattern matching for Javascript
JavaScript
774
star
2

adt.js

Algebraic data types for Javascript
JavaScript
220
star
3

purescript-run

An extensible-effects implementation
PureScript
157
star
4

purescript-spork

Elm-like for PureScript
PureScript
157
star
5

purescript-variant

Polymorphic variants for PureScript
PureScript
132
star
6

adt-simple

Algebraic data types for JavaScript using Sweet.js macros
JavaScript
94
star
7

purescript-tidy

A syntax tidy-upper for PureScript.
PureScript
91
star
8

purescript-psa

Error/Warning reporting frontend for the PureScript compiler
PureScript
88
star
9

purescript-routing-duplex

Unified parsing and printing for routes in PureScript
PureScript
86
star
10

lambda-chop

Sweet.js macros for lambdas with currying, bound functions, and placeholders.
JavaScript
83
star
11

purescript-checked-exceptions

Extensible checked exceptions with polymorphic variants
PureScript
80
star
12

example-functional-compiler

PureScript
59
star
13

purescript-heterogeneous

Maps and folds for heterogeneous data types.
PureScript
54
star
14

purescript-language-cst-parser

PureScript CST Parser written in PureScript
PureScript
49
star
15

tailrec.js

Dead simple auto-trampolining for Javascript
JavaScript
47
star
16

purescript-typelevel-eval

Higher order functional programming in PureScript's type system
PureScript
43
star
17

purescript-cst

A concrete-syntax tree and parser for the PureScript language
Haskell
40
star
18

purescript-convertable-options

Highly-overloaded APIs for PureScript
PureScript
35
star
19

purescript-call-by-name

Syntactically light-weight call-by-name arguments in PureScript. No guarantees. Completely gratuitous.
PureScript
30
star
20

purescript-dodo-printer

An adequate printer.
PureScript
30
star
21

purescript-tidy-codegen

Convenient codegen for PureScript
PureScript
28
star
22

grunt-sweet.js

Grunt task for Sweet.js
JavaScript
27
star
23

purescript-argparse-basic

A no frills CLI argument parser for PureScript.
PureScript
20
star
24

purescript-optimizer

Haskell
20
star
25

purescript-node-workerbees

Convenient multi-threading on Node with PureScript.
PureScript
17
star
26

purescript-run-streaming

Streaming effects for PureScript
PureScript
15
star
27

polykinds

Experimental polykinds implementation
Haskell
14
star
28

purescript-node-glob-basic

A very basic glob library for PureScript.
PureScript
11
star
29

derelicte

An AltJS lang implemented entirely with Sweet.js macros
JavaScript
9
star
30

purescript-free-semigroupoid

Free semigroupoids for PureScript
PureScript
9
star
31

jsScrollbar

A highly customizable javascript scrollbar
JavaScript
9
star
32

purescript-higher-order

PureScript
8
star
33

talks

PureScript
8
star
34

purescript-halogen-connect-experiment

PureScript
5
star
35

purescript-halogen-startapp

PureScript
5
star
36

backbone.ext

Extensions for Backbone.js
JavaScript
3
star
37

purescript-psa-utils

Utility library for purescript-psa
PureScript
2
star