Pilaf is a simple pattern-matching library for JavaScript which provides recursive pattern-matching in the style of Erlang.
If you're tired of writing chains of if, else and switch statements,
Pilaf can help you structure your code in the pattern-matching style popular in
languages like Erlang and Haskell.
Pilaf is implemented in less than 200 lines of vanilla JavaScript and is a standalone library with no any dependencies.
Here is a simple recursive function to calculate the sum of an array written in traditional JavaScript
function sum(a) {
if (a.length === 0) {
return 0;
}
else if (a.length === 1) {
return a[0];
}
else {
return a[0] + sum(a.slice(0));
}
}The same function can be written using Pilaf as
function sum(a) {
return match(a)
.case([]) .run(x => 0)
.case(["$element"]) .run(x => x.element)
.case(["$head", "...$tail"]) .run(x => x.head + sum(x.tail))
.return();
}Pilaf provides match which takes a value to be matched against and
returns an objects which allows chaining of case and run calls. case
takes a pattern and tries to match it against the value. If the match passes,
run is executed which takes a callback and executes the callback on the match
result from case.
Even in this very simple example, it is easy to see that the declarative style
which employs patterns is more concise and clear than imperative style which
uses if and else.
Pilaf provides when which acts like guard in Erlang and filters the value if
the match is successful before executing run.
The above function can also be written using when as
function sum(a) {
return match(a)
.case([]) .run(x => 0)
.case(["$head", "...$tail"])
.when(x => x.tail.length === 0) .run(x => x.head)
.otherwise() .run(x => x.head + sum(x.tail))
.return();
}
Pilaf also provides check which just matches one pattern against one value.
Pilaf features different types of patterns which can be combined arbitratily
effectively avoiding the need to use if, else and switch in the code.
Variable patterns begin with "$". The string after the "$" is the key which
stores the value which can be accessed in when filters and run callbacks.
Variable patterns always match succesfully against any value.
> match(2).case("$a").run(x => x.a + 7).return()
9
> match("world").case("$a").run(x => "hello, " + x.b).return()
hello, world
> check("$a", [ 2, 5, 8 ])
{ __match__: true, a: [ 2, 5, 8 ] }
> check("$a", { author: "J.R.R. Tolkien", books: [ "The Hobbit", "The Lord of the Rings" ] })
{ __match__: true, a: { author: "J.R.R. Tolkien", books: [ "The Hobbit", "The Lord of the Rings" ] } }Literal patterns match literals against values. They match successfully if the pattern is exactly equal to the value
> check(2, 2)
{ __match__: true }
> check(2, 3)
{ __match__: false }Wildcard patterns begin with "_" and match any value just like variable
patterns but discard the result. They are used when the result is not needed in
further computation.
> check("_a", [ 3, 7, "world", true ])
{ __match__: true }Array patterns are patterns which recursively match against arrays.
> check([ 2, 7, "$a" ], [ 2, 7, 11 ])
{ __match__: true, a: 11 }
> check([ 2, 7, "$a" ], [ 3, 5, 11 ])
{ __match__: false }
> check([ [ "$a", [ "$b", 7 ] ], "$c" ], [ [ 2, [ "hello", 7 ] ] , "world" ])
{ __match__: true, a: 2, b: "hello", c: "world" }Array patterns can also be used to match against arrays when the length of the
array is unknown. If the last element of the array pattern starts with "...$",
it binds to the rest of the array.
> check([ 2, 3, "...$a" ], [ 2, 3, 7, true, "hello", 17, 19 ])
{ __match__: true, a: [ 7, true, "hello", 17, 19 ] }Object patterns work just like array patterns, but take objects as patterns.
> check(
> { book: "$a", author: "Lewis Carroll" },
> { book: "Alice in Wonderland", author: "Lewis Carroll" }
> )
{ __match__: true, a: "Alice in Wonderland" }
> check(
> { author: "$a", books: [ "The Hobbit", "...$b" ] },
> { author: "J.R.R. Tolkien", books: [ "The Hobbit", "The Lord of the Rings", "The Silmarillion" ] }
> )
{
__match__: true,
a: "J.R.R. Tolkien",
b: [ "The Lord of the Rings", "The Silmarillion" ]
}Pilaf is heavily inspired from Match-Toy.
Match-Toy takes patterns as strings and parses them into structures. Pilaf avoids parsing as it takes JavaScript literals as patterns. Hence, Match-Toy features more patterns and is larger in size than Pilaf.
Feel free to provide feedback, report issues and request new features. You can also mail me at irfan@irfanali.org.
GNU General Public License v3.0