Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
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
- Host: GitHub
- URL: https://github.com/egison/sweet-egison
- Owner: egison
- License: bsd-3-clause
- Created: 2020-02-26T08:26:51.000Z (almost 5 years ago)
- Default Branch: master
- Last Pushed: 2022-01-23T05:25:38.000Z (almost 3 years ago)
- Last Synced: 2024-10-28T17:13:32.022Z (2 months ago)
- Topics: backtracking, egison, haskell, non-linear-pattern, pattern-matching
- Language: Haskell
- Homepage:
- Size: 814 KB
- Stars: 17
- Watchers: 5
- Forks: 1
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
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