diff --git a/DEMO.ipynb b/DEMO.ipynb
new file mode 100644
index 0000000..afdd81d
--- /dev/null
+++ b/DEMO.ipynb
@@ -0,0 +1,710 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "![Pampy in Star Wars](https://raw.githubusercontent.com/santinic/pampy/master/imgs/pampy.png \"Pampy in Star Wars\")\n",
+ "\n",
+ "# Pampy: Pattern Matching for Python\n",
+ "[![License MIT](https://go-shields.herokuapp.com/license-MIT-blue.png)]()\n",
+ "[![Travis-CI Status](https://api.travis-ci.org/santinic/pampy.svg?branch=master)](https://travis-ci.org/santinic/pampy)\n",
+ "[![Coverage Status](https://coveralls.io/repos/github/santinic/pampy/badge.svg?branch=master)](https://coveralls.io/github/santinic/pampy?branch=master)\n",
+ "[![PyPI version](https://badge.fury.io/py/pampy.svg)](https://badge.fury.io/py/pampy)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Pampy is pretty small (150 lines), reasonably fast, and often makes your code more readable\n",
+ "and hence easier to reason about.\n",
+ "\n",
+ "\n",
+ " \n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## You can write many patterns\n",
+ "\n",
+ "Patterns are evaluated in the order they appear.\n",
+ "\n",
+ "\n",
+ " \n",
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## You can write Fibonacci\n",
+ "\n",
+ "The operator _ means \"any other case I didn't think of\"."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pampy import match, _\n",
+ "\n",
+ "def fibonacci(n):\n",
+ " return match(n,\n",
+ " 1, 1,\n",
+ " 2, 1,\n",
+ " _, lambda x: fibonacci(x-1) + fibonacci(x-2)\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "55"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "fibonacci(10)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## You can write a Lisp calculator in 5 lines"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from functools import reduce\n",
+ "from pampy import match, REST, _\n",
+ "\n",
+ "def lisp(exp):\n",
+ " return match(exp,\n",
+ " int, lambda x: x,\n",
+ " callable, lambda x: x,\n",
+ " (callable, REST), lambda f, rest: f(*map(lisp, rest)),\n",
+ " tuple, lambda t: list(map(lisp, t)),\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "plus = lambda a, b: a + b\n",
+ "minus = lambda a, b: a - b"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "3"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "lisp((plus, 1, 2))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "3"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "lisp((plus, 1, (minus, 4, 2)))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "45"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "lisp((reduce, plus, (range, 10)))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## You can match so many things!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "```python\n",
+ "match(x,\n",
+ " 3, \"this matches the number 3\",\n",
+ "\n",
+ " int, \"matches any integer\",\n",
+ "\n",
+ " (str, int), lambda a, b: \"a tuple (a, b) you can use in a function\",\n",
+ "\n",
+ " [1, 2, _], \"any list of 3 elements that begins with [1, 2]\",\n",
+ "\n",
+ " {'x': _}, \"any dict with a key 'x' and any value associated\",\n",
+ "\n",
+ " _, \"anything else\"\n",
+ ")\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## You can match [HEAD, TAIL]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pampy import match, HEAD, TAIL, _\n",
+ "\n",
+ "x = [1, 2, 3]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[2, 3]"
+ ]
+ },
+ "execution_count": 16,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "match(x, [1, TAIL], lambda t: t)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(1, [2, 3])"
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "match(x, [HEAD, TAIL], lambda h, t: (h, t))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "`TAIL` and `REST` actually mean the same thing."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## You can nest lists and tuples"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[1, [2, 3], 4]"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from pampy import match, _\n",
+ "\n",
+ "x = [1, [2, 3], 4]\n",
+ "\n",
+ "match(x, [1, [_, 3], _], lambda a, b: [1, [a, 3], b])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## You can nest dicts. And you can use _ as key!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "3"
+ ]
+ },
+ "execution_count": 19,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pet = { 'type': 'dog', 'details': { 'age': 3 } }\n",
+ "\n",
+ "match(pet, { 'details': { 'age': _ } }, lambda age: age)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "('details', 3)"
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "match(pet, { _ : { 'age': _ } }, lambda a, b: (a, b))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "It feels like putting multiple _ inside dicts shouldn't work. Isn't ordering in dicts not guaranteed ?\n",
+ "But it does because\n",
+ "[in Python 3.7, dict maintains insertion key order by default](https://mail.python.org/pipermail/python-dev/2017-December/151283.html)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## You can match class hierarchies"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Pet: pass\n",
+ "class Dog(Pet): pass\n",
+ "class Cat(Pet): pass\n",
+ "class Hamster(Pet): pass\n",
+ "\n",
+ "def what_is(x):\n",
+ " return match(x,\n",
+ " Dog, \t\t'dog',\n",
+ " Cat, \t\t'cat',\n",
+ " Pet, \t\t'any other pet',\n",
+ " _, \t\t'this is not a pet at all',\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'cat'"
+ ]
+ },
+ "execution_count": 22,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "what_is(Cat())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'dog'"
+ ]
+ },
+ "execution_count": 23,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "what_is(Dog())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'any other pet'"
+ ]
+ },
+ "execution_count": 24,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "what_is(Hamster())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'any other pet'"
+ ]
+ },
+ "execution_count": 25,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "what_is(Pet())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'this is not a pet at all'"
+ ]
+ },
+ "execution_count": 26,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "what_is(42)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## All the things you can match\n",
+ "\n",
+ "As Pattern you can use any Python type, any class, or any Python value.\n",
+ "\n",
+ "The operator `_` and built-in types like `int` or `str`, extract variables that are passed to functions.\n",
+ "\n",
+ "Types and Classes are matched via `instanceof(value, pattern)`.\n",
+ "\n",
+ "`Iterable` Patterns match recursively through all their elements. The same goes for dictionaries.\n",
+ "\n",
+ "| Pattern Example | What it means | Matched Example | Arguments Passed to function | NOT Matched Example |\n",
+ "| --------------- | --------------| --------------- | ----------------------------- | ------------------ |\n",
+ "| `\"hello\"` | only the string `\"hello\"` matches | `\"hello\"` | nothing | any other value |\n",
+ "| `None` | only `None` | `None` | nothing | any other value |\n",
+ "| `int` | Any integer | `42` | `42` | any other value |\n",
+ "| `float` | Any float number | `2.35` | `2.35` | any other value |\n",
+ "| `str` | Any string | `\"hello\"` | `\"hello\"` | any other value |\n",
+ "| `tuple` | Any tuple | `(1, 2)` | `(1, 2)` | any other value |\n",
+ "| `list` | Any list | `[1, 2]` | `[1, 2]` | any other value |\n",
+ "| `MyClass` | Any instance of MyClass. **And any object that extends MyClass.** | `MyClass()` | that instance | any other object |\n",
+ "| `_` | Any object (even None) | | that value | |\n",
+ "| `ANY` | The same as `_` | | that value | |\n",
+ "| `(int, int)` | A tuple made of any two integers | `(1, 2)` | `1` and `2` | (True, False) |\n",
+ "| `[1, 2, _]` | A list that starts with 1, 2 and ends with any value | `[1, 2, 3]` | `3` | `[1, 2, 3, 4]` |\n",
+ "| `[1, 2, TAIL]` | A list that start with 1, 2 and ends with any sequence | `[1, 2, 3, 4]`| `[3, 4]` | `[1, 7, 7, 7]` |\n",
+ "| `{'type':'dog', age: _ }` | Any dict with `type: \"dog\"` and with an age | `{\"type\":\"dog\", \"age\": 3}` | `3` | `{\"type\":\"cat\", \"age\":2}` |\n",
+ "| `{'type':'dog', age: int }` | Any dict with `type: \"dog\"` and with an `int` age | `{\"type\":\"dog\", \"age\": 3}` | `3` | `{\"type\":\"dog\", \"age\":2.3}` |\n",
+ "| `re.compile('(\\w+)-(\\w+)-cat$')` | Any string that matches that regular expression expr | `\"my-fuffy-cat\"` | `\"my\"` and `\"puffy\"` | `\"fuffy-dog\"` | "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Using strict=False\n",
+ "\n",
+ "By default `match()` is strict. If no pattern matches, it raises a `MatchError`.\n",
+ "\n",
+ "You can prevent it using `strict=False`. In this case `match` just returns `False` if nothing matches."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {},
+ "outputs": [
+ {
+ "ename": "MatchError",
+ "evalue": "'_' not provided. This case is not handled:\n[1, 2]",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mMatchError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmatch\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m3\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"whatever\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;32m/usr/local/lib/python3.6/site-packages/pampy/pampy.py\u001b[0m in \u001b[0;36mmatch\u001b[0;34m(var, strict, *args)\u001b[0m\n\u001b[1;32m 155\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mstrict\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 156\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0m_\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mpatterns\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 157\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mMatchError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"'_' not provided. This case is not handled:\\n%s\"\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvar\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 158\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 159\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mMatchError\u001b[0m: '_' not provided. This case is not handled:\n[1, 2]"
+ ]
+ }
+ ],
+ "source": [
+ "match([1, 2], [1, 2, 3], \"whatever\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "execution_count": 29,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "match([1, 2], [1, 2, 3], \"whatever\", strict=False)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Using Regular Expressions\n",
+ "Pampy supports Python's Regex. You can pass a compiled regex as pattern, and Pampy is going to run `patter.search()`, and then pass to the action function the result of `.groups()`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {
+ "attributes": {
+ "classes": [
+ "python "
+ ],
+ "id": ""
+ }
+ },
+ "outputs": [],
+ "source": [
+ "import re\n",
+ "\n",
+ "def what_is(pet):\n",
+ " return match(pet,\n",
+ " re.compile('(\\w+)-(\\w+)-cat$'), lambda name, my: 'cat '+name,\n",
+ " re.compile('(\\w+)-(\\w+)-dog$'), lambda name, my: 'dog '+name,\n",
+ " _, \"something else\"\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'dog fuffy'"
+ ]
+ },
+ "execution_count": 33,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "what_is('fuffy-my-dog')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'dog puffy'"
+ ]
+ },
+ "execution_count": 34,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "what_is('puffy-her-dog')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'cat carla'"
+ ]
+ },
+ "execution_count": 35,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "what_is('carla-your-cat')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'something else'"
+ ]
+ },
+ "execution_count": 36,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "what_is('roger-my-hamster')"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.6.5"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/README.md b/README.md
index 13970e9..d71acd1 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,12 @@ and hence easier to reason about. [There is also a JavaScript version, called Pa
+## Demo
+
+Try `pampy` interactively in this online demo:
+
+[![](https://cdn-images-1.medium.com/max/1600/1*cI91DR6og9iF06hBrHKINg.png)](https://notebooks.ai/demo/gh/santinic/pampy)
+
## You can write many patterns
Patterns are evaluated in the order they appear.
diff --git a/demo.yml b/demo.yml
new file mode 100644
index 0000000..b2c6e6f
--- /dev/null
+++ b/demo.yml
@@ -0,0 +1,2 @@
+requirements:
+ - pampy==0.1.9
\ No newline at end of file