Ecosyste.ms: Awesome

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

Awesome Lists | Featured Topics | Projects

https://github.com/egison/sweet-egison

Haskell library for non-deterministic pattern matching
https://github.com/egison/sweet-egison

backtracking egison haskell non-linear-pattern pattern-matching

Last synced: about 2 months ago
JSON representation

Haskell library for non-deterministic pattern matching

Awesome Lists containing this project

README

        

# Sweet Egison

[![Actions Status](https://github.com/egison/sweet-egison/workflows/latest/badge.svg)](https://github.com/egison/sweet-egison/actions?workflow=latest)
[![Actions Status](https://github.com/egison/sweet-egison/workflows/release/badge.svg)](https://github.com/egison/sweet-egison/actions?workflow=release)
[![Hackage](https://img.shields.io/hackage/v/sweet-egison.svg)](https://hackage.haskell.org/package/sweet-egison)
[![Hackage Deps](https://img.shields.io/hackage-deps/v/sweet-egison.svg)](http://packdeps.haskellers.com/reverse/sweet-egison)

The [Sweet Egison](https://hackage.haskell.org/package/sweet-egison) is a shallow embedding implementation of non-linear pattern matching with extensible and polymorphic patterns [1].
This library desguars the [Egison](https:///www.egison.org) pattern-match expressions into Haskell programs that use [non-deterministic monads](https://github.com/egison/backtracking).
This library provides a base of the pattern-match-oriented (PMO) programming style [2] for Haskell users at a practical level of efficiency.

## Getting started

We code the equivalent pattern match of `case [1, 2, 3] of x : xs -> (x, xs)` in this library as follows:

```haskell
> matchAll dfs [1, 2, 3] (List Something) [[mc| $x : $xs -> (x, xs) |]]
[(1,[2,3])]
```

Here, we can only observe the small syntactic difference in pattern expressions: the variable bindings are prefixed with `$`. (We'll come back to `List Something` later.)
You may notice that `matchAll` returns a list.
In our library, pattern matching can return many results.
See the following example that doubles all elements in a list:

```haskell
> take 10 $ matchAll dfs [1 ..] (List Something) [[mc| _ ++ $x : _ -> x * 2 |]]
[2,4,6,8,10,12,14,16,18,20]
```

`++` is the *join* operator that decomposes a list into an initial prefix and the remaining suffix.
We can implement `map` with pattern matching using this:

```haskell
> map f xs = matchAll dfs xs (List Something) [[mc| _ ++ $x : _ -> f x |]]
> map (*2) [1,2,3]
[2,4,6]
```

Note that we don't see any recursions or `fold`s in our `map` definition! An intuition of `map` function, that applies the function to all elements, are expressed directly in the pattern expression.

### Matchers

Because our pattern matching can return many results, we can use it to decompose *non-free data types* such as multisets and sets.
For example:

```haskell
> matchAll dfs [1, 2, 3] (Multiset Something) [[mc| $x : $xs -> (x, xs) |]]
[(1,[2,3]),(2,[1,3]),(3,[1,2])]
```

We use `Multiset Something` instead of `List Something` here to match the target `[1, 2, 3]` as a multiset.
These parameters such as `Multiset Something`, `List (List Something)`, and `Something` are called *matchers* and specify pattern-matching methods.
Given a matcher `m`, `Multiset m` is a matcher for multisets that matches its elements with `m`.
`Something` is a matcher that provides simple matching methods for an arbitrary value.
Pattern constructors such as `:` and `++` are overloaded over matchers for collections to archive the ad-hoc polymorphism of patterns.

### Controlling matching strategy

Some pattern matching have infinitely many results and `matchAll bfs` is designed to be able to enumerate all the results.
For this purpose, `matchAll bfs` traverses a search tree for pattern matching in the breadth-first order.
The following example illustrates this:

```haskell
> take 10 $ matchAll bfs [1 ..] (Set Something) [[mc| $x : $y : _ -> (x, y) |]]
[(1,1),(2,1),(1,2),(3,1),(1,3),(2,2),(1,4),(4,1),(1,5),(2,3)]
```

We can use the depth-first search with `matchAll dfs`.

```haskell
> take 10 $ matchAll dfs [1 ..] (Set Something) [[mc| $x : $y : _ -> (x, y) |]]
[(1,1),(1,2),(1,3),(1,4),(1,5),(1,6),(1,7),(1,8),(1,9),(1,10)]
```

In most cases, the depth-first search is faster than the default breadth-first search strategy.
It is recommended to always use `matchAll dfs` if it is OK to do so.

With `matchAll dfs`, we can define an intuitive pattern-matching version of `concat` function on lists.

```haskell
> concat xs = matchAll dfs xs (List (List Something)) [[mc| _ ++ (_ ++ $x : _) : _ -> x |]]
> concat [[1,2], [3,4,5]]
[1,2,3,4,5]
```

### Non-linear patterns

The non-linear pattern is another powerful pattern-matching feature.
It allows us to refer the value bound to variables appear in the left side of the pattern.
We provide a pattern syntax named value patterns in the form of `#e`.
The `Eql` matcher enables value patterns to match with targets that are equal to the corresponding expression.
For example, the following example enumerates (p, p+2) pairs of primes:

```haskell
> import Data.Numbers.Primes ( primes )
> take 10 $ matchAll bfs primes (List Eql) [[mc| _ ++ $p : #(p + 2) : _ -> (p, p+2) |]]
[(3,5),(5,7),(11,13),(17,19),(29,31),(41,43),(59,61),(71,73),(101,103),(107,109)]
```

We can implement a pattern-matching version of set functions such as `member` and `intersect` in a declarative way using non-linear patterns.
Match clauses are monoids and can be concatenated using `<>`.

```haskell
> member x xs = match dfs xs (Multiset Eql) [[mc| #x : _ -> True |], [mc| _ -> False |]]
> member 1 [3,4,1,4]
True
> intersect xs ys = matchAll dfs (xs, ys) (Pair (Set Eql) (Set Eql)) [[mc| ($x : _, #x : _) -> x |]]
> intersect [1,2,3] [4,5,3,2]
[2,3]
```

### Further readings

Some practical applications of PMO such as a [SAT solver](https://github.com/egison/sweet-egison/blob/master/example/cdcl.hs) are placed under [example/](https://github.com/egison/sweet-egison/blob/master/example/).
Detailed information of Egison, the original PMO language implementation, can be found on [https://www.egison.org/](https://www.egison.org/) or in [1].
You can learn more about pattern-match-oriented programming style in [2].

## Implementation

Sweet Egison transform patterns into a program that uses non-deterministic monads.
Our quasi-quoter `mc` translates match clauses into functions that take a target and return a non-deterministic computation as `MonadPlus`-like monadic expression.
As `MonadPlus` can express backtracking computation, we can perform efficient backtracking pattern matching.
For example, the match clause `[mc| $x : #(x + 10) : _ -> (x, x + 10) |]` is transformed as follows:
```haskell
\ (mat_a5sV, tgt_a5sW)
-> let (tmpM_a5sX, tmpM_a5sY) = (consM mat_a5sV) tgt_a5sW
in
((fromList (((cons (GP, GP)) mat_a5sV) tgt_a5sW))
>>=
(\ (tmpT_a5sZ, tmpT_a5t0)
-> let x = tmpT_a5sZ in
let (tmpM_a5t1, tmpM_a5t2) = (consM tmpM_a5sY) tmpT_a5t0
in
((fromList (((cons (GP, WC)) tmpM_a5sY) tmpT_a5t0))
>>=
(\ (tmpT_a5t3, tmpT_a5t4)
-> ((fromList ((((value (x + 10)) ()) tmpM_a5t1) tmpT_a5t3))
>>= (\ () -> pure (x, x + 10)))))))
```
The infix operators `:` and `++` are synonyms of `cons` and `join`, respectively, and desugared in that way during translation.

The `matchAll` function is defined as a function that creates and passes the argument for this non-deterministic monads.
```haskell
matchAll strategy target matcher =
concatMap (\b -> toList (strategy (matcher, target) >>= b))
```

Consequently, the pattern-match expression
```haskell
matchAll dfs [1, 2, 3, 12] (Multiset Eql)
[[mc| $x : #(x + 10) : _ -> (x, x + 10) |]]
-- [(2, 12)]
```
is transformed into a program that is equivalent to the following:
```haskell
concatMap (\b -> toList (dfs (Multiset Eql, [1, 2, 3, 12]) >>= b))
[\ (mat_a5sV, tgt_a5sW)
-> let (tmpM_a5sX, tmpM_a5sY) = (consM mat_a5sV) tgt_a5sW
in
((fromList (((cons (GP, GP)) mat_a5sV) tgt_a5sW))
>>=
(\ (tmpT_a5sZ, tmpT_a5t0)
-> let x = tmpT_a5sZ in
let (tmpM_a5t1, tmpM_a5t2) = (consM tmpM_a5sY) tmpT_a5t0
in
((fromList (((cons (GP, WC)) tmpM_a5sY) tmpT_a5t0))
>>=
(\ (tmpT_a5t3, tmpT_a5t4)
-> ((fromList ((((value (x + 10)) ()) tmpM_a5t1) tmpT_a5t3))
>>= (\ () -> pure (x, x + 10)))))))]
```

### MiniEgison (Deep Embedding) vs. Sweet Egison (Shallow Embedding)

[miniEgison](https://github.com/egison/egison-haskell) is also a Haskell library that implements Egison pattern matching.
The main difference between [miniEgison](https://github.com/egison/egison-haskell) and Sweet Egison is that Sweet Egison translates pattern matching into Haskell control expressions (shallow embedding), whereas [miniEgison](https://github.com/egison/egison-haskell) translates it into Haskell data expressions (deep embedding).
As a result, Sweet Egison is faster than miniEgison.
The following benchmark is taken using MacBook Pro (2017, 2.3 GHz Intel Core i5).

| | comb2 (n = 15000) | perm2 (n = 5000) | CDCL (50 vars) |
|--------------|-------------------|------------------|----------------|
| miniEgison | 13.029 sec | 3.854 sec | 1.025 sec |
| Sweet Egison | 0.303 sec | 0.462 sec | 0.097 sec |

There is almost no execution performance differences between programs written using list comprehensions and Sweet Egison.

| | comb2 (n = 15000) | comb2 (n = 15000) | perm2 (n = 5000) | perm2 (n = 10000) |
|---------------------|-------------------|-------------------|------------------|-------------------|
| List Comprehensions | 0.347 sec | 1.244 sec | 0.409 sec | 2.077 sec |
| Sweet Egison | 0.309 sec | 1.081 sec | 0.434 sec | 1.984 sec |

Programs used for the above benchmarks are follows:
* [sample/comb2.hs](https://github.com/egison/sweet-egison/blob/master/sample/comb2.hs)
* [sample/perm2.hs](https://github.com/egison/sweet-egison/blob/master/sample/perm2.hs)
* [sample/cdcl.hs](https://github.com/egison/sweet-egison/blob/master/sample/cdcl.hs)

## Bibliography

- [1] Satoshi Egi and Yuichi Nishiwaki: Functional Programming in Pattern-Match-Oriented Programming Style, The Art, Science, and Engineering of Programming, 2020, Vol. 4, Issue 3, Article 7, DOI: 10.22152/programming-journal.org/2020/4/7
- [2] Satoshi Egi and Yuichi Nishiwaki: Non-linear Pattern Matching with Backtracking for Non-free Data Types, APLAS 2018 - Asian Symposium on Programming Languages and Systems, DOI: 11.1007/978-3-030-02768-1_1