Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/missingfaktor/match-block

[ABANDONED] Pattern matching blocks as values
https://github.com/missingfaktor/match-block

Last synced: about 1 month ago
JSON representation

[ABANDONED] Pattern matching blocks as values

Awesome Lists containing this project

README

        

# match-block

## NOTICE: This project has been abandoned.

### Pattern matching blocks as values.



![data all the things](http://i.imgur.com/6OupF6Q.jpg)

Turning abstractions into first-class values gives us an ability to abstract over and compose them, and often yields conceptually simpler models. First-class-ing things has been a common theme in Clojure, where it's fondly referred to as "data all the things".

Pattern matching is a lovely feature. It's an integral part of all major functional languages. Sadly, in most implementations, pattern matching blocks aren't first class values.

Scala innovates in this area by treating pattern-matching blocks as first-class values. It achieves this by providing a dedicated type (called `PartialFunction`) for them. Let's see how this can be useful.

## PartialFunction in Scala

Consider the following piece of Scala code (pasted directly from a REPL session):

```scala
scala> def foo(a: String, b: String) = try {
| a.toInt / b.toInt
| } catch {
| case ex: NumberFormatException => 'nfe
| case ex: ArithmeticException => 'ae
| }
foo: (a: String, b: String)Any

scala> foo("2", "1")
res6: Any = 2

scala> foo("kl", "1")
res7: Any = 'nfe

scala> foo("9", "0")
res8: Any = 'ae
```

Syntactically, the `try`-`catch` here looks fairly similar to its Clojure counterpart. However there is one big difference. The bit that's passed to `catch` as argument is a first-class value! This lets us do things like:

```scala
scala> def foo(a: String, b: String, handler: PartialFunction[Throwable, Any]) = try {
| a.toInt / b.toInt
| } catch handler
foo: (a: String, b: String, handler: PartialFunction[Throwable,Any])Any

scala> val h: PartialFunction[Throwable, Any] = {
| case ex: NumberFormatException => 'nfe
| }
h: PartialFunction[Throwable,Any] =

scala> val i: PartialFunction[Throwable, Any] = {
| case ex: ArithmeticException => 'ae
| }
i: PartialFunction[Throwable,Any] =

scala> foo("4", "0", h.orElse(i))
res9: Any = 'ae
```

And even:

```scala
scala> attempt {
| val s = Console.readLine
| s.toInt
| } fallback {
| case ex: NumberFormatException => println("Invalid string. Try again."); restart
| }

// "hobo"
Invalid string. Try again.

// "kucuk"
Invalid string. Try again.

// "9"
res12: Int = 9

scala>
```

*(You can find the implementation for the `attempt`-`fallback` utility [here](http://blog.engineering.vayana.in/).)*

From these examples, we can deduce two major advantages of first-class pattern matching blocks:

- One can compose pattern matching blocks using combinators such as `orElse`.
- It's easy to create new constructs requiring case-based handling, without having to resort to ad hoc syntactic transformations (macros).

There are numerous examples in the Scala world where this has been put to a good use. Some of which are as follows:

- [`scala.util.control.Exception`](http://www.scala-lang.org/api/current/index.html#scala.util.control.Exception$) - Compositional and functional goodness, atop traditional exception handling constructs.
- Standard collection operations like `collect`.
- Methods such as [`onSuccess`](http://docs.scala-lang.org/overviews/core/futures.html#callbacks) for registering callbacks in futures library.
- Error recovery combinators, such as [`recover`](http://docs.scala-lang.org/overviews/core/futures.html#functional_composition_and_forcomprehensions) in futures library.
- [`react`](http://docs.scala-lang.org/overviews/core/actors.html) block in actors.
- [Request matchers](http://simply.liftweb.net/index-Chapter-11.html) in Lift.

## How things look on the Clojure side

Clojure currently does not have a generic construct of this sort. Clojure's `try`-`catch` for example, is an ad hoc syntax, which maps almost directly to its Java counterpart.

As it happens, Clojure has everything you will need to implement this idea on your own:

- First-class functions.
- [`core.match`](https://github.com/clojure/core.match) - a great pattern matching library to piggyback on.
- Support for building syntactic extensions (by virtue of being a Lisp).

This project uses the above to provide first-class pattern matching blocks implementation for Clojure.

The implementation is almost entirely based on Scala's `PartialFunction` - the core implementation, the optimizations, and the combinators.

## What the project currently does

The REPL session below should demystify the crux of the library:

```clojure
user=> (use '[clojure.core.match :only (match)]) (use 'match-block.core)
nil
nil
user=> (macroexpand-1 '(match-block [a b] [2 :two] :qux [3 :three] :guz))
(match-block.core/map->MatchBlock {:fun (clojure.core/fn [a b]
(clojure.core.match/match [a b]
[2 :two] :qux
[3 :three] :guz))
:defined-at? (clojure.core/fn [a b]
(clojure.core.match/match [a b]
[2 :two] true
[3 :three] true
:else false))})

```

## Future directions for the library

- Combinators to compose, transform pattern matching blocks. Examples: `or-else`, `comp`, `apply-or-else`, `lift`, `unlift`, `cond` etc.
- Scala's `PartialFunction`s have more special treatment in compiler, making the above-mentioned combinators very efficient. We could borrow some of those ideas in this port.
- A variant of `try`-`catch` that accepts its handler as a pattern matching block.
- The [`slingshot`](https://github.com/scgilardi/slingshot) library has a concept of "selectors". I think "selectors" are simply a special case of "matching", and matching should belong in `core.match`. The selectors could likely be reimplemented with a bunch of custom `core.match` patterns, plus `match-block`.
- The implementation could potentially make use of knowledge of `core.match` innards to provide faster implementations of `:fun` and `:defined-at?`.

## Usage

(REPL session again.)

```clojure
user=> (use '[clojure.core.match :only (match)]) (use 'match-block.core)
nil
nil
user=> (def foo
#_=> (match-block [a b]
#_=> [3 1] :nice
#_=> :else :aww-shucks))
#'user/foo
user=> (foo 3 1)
:nice
user=> (foo 3 2)
:aww-shucks
user=> (defined-at? foo 3 1)
true
user=> (defined-at? foo 3 3)
true
user=> (def bar
#_=> (match-block [a b]
#_=> [3 1] :nice))
#'user/bar
user=> (bar 3 3)

IllegalArgumentException No matching clause: 3 3 user/fn--2410 (NO_SOURCE_FILE:2)
user=> (defined-at? bar 3 3)
false
user=> Bye for now!%
```

## Inputs welcome!

Any sort of feedback, code review, pull requests are most welcome!

## License

Copyright © 2014 Rahul Goma Phulore.

Distributed under the Eclipse Public License, the same as Clojure.