| Name | microkanren JSON |
| Version |
0.4.4
JSON |
| download |
| home_page | |
| Summary | A Python implementation of microkanren extended with constraints |
| upload_time | 2023-10-25 13:47:56 |
| maintainer | |
| docs_url | None |
| author | |
| requires_python | <3.13,>=3.11 |
| license | MIT License Copyright (c) 2023 Joshua Munn Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| keywords |
|
| VCS |
 |
| bugtrack_url |
|
| requirements |
No requirements were recorded.
|
| Travis-CI |
No Travis.
|
| coveralls test coverage |
No coveralls.
|
# microkanren
`microkanren` is an implementation of a miniKanren style relational programming language, embedded in Python. The solver is implemented in the style of μKanren[^1]. It provides a framework for extending the language with constraints, as well as a basic implementation of disequality and finite domain constraints, in the style of cKanren[^2].
Due to the differences between Python and the reference implementation languages (Scheme, Racket), some divergences from the typical miniKanren API are necessary. It is a goal to capture the spirit of the miniKanren language family, but not the exact API.
* [Installation](#installation)
* [Usage](#usage)
+ [Basic usage](#basic-usage)
+ [Conjunction and disjunction](#conjunction-and-disjunction)
+ [The result type and multiple top-level variables](#the-result-type-and-multiple-top-level-variables)
+ [Defining goal constructors](#defining-goal-constructors)
- [Recursive goal constructors and `snooze` (Zzz)](#recursive-goal-constructors-and--snooze---zzz-)
* [Developing microkanren](#developing-microkanren)
## Installation
``` bash
pip install microkanren
```
## Usage
### Basic usage
The basic goal constructor is `eq`. `eq` takes two terms as arguments, and returns a goal that will succeed if the terms can be unified, and fails otherwise.
``` python-console
>>> from microkanren import eq
>>> eq("🍕", "🍕")
<microkanren.core.Goal object at 0x7f07d85cced0>
```
To run a goal, use one of the provided interfaces: `run`, `run_all`, or `irun`. `run` takes two arguments:
1. an integer, the maximum number of results to return; and
2. a callable with positional-only arguments, each of which will receive a fresh logic variable.
`run_all` and `irun` take a single argument, the fresh-var-receiver.
``` python-console
>>> from microkanren import run
>>> run(1, lambda x: eq(x, "🍕"))
['🍕']
```
The return type of `run` and `run_all` is a (possibly-empty) list of results. If the list is empty, there are no solutions that satisfy the goal. `irun` returns a generator that yields single results.
``` python-console
>>> from microkanren import irun
>>> rs = irun(lambda x: eq(x, "😁"))
>>> next(rs)
'😁'
>>> next(rs)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
```
### Conjunction and disjunction
Conjunction and disjunction are provided by the vararg `conj` and `disj` functions. `Goal` objects support combination using `|` and `&` operators, which map to `conj` and `disj`.
``` python-console
>>> from microkanren import run_all
>>> run_all(lambda x: disj(eq(x, "α"), eq(x, "β"), eq(x, "δ")))
['α', 'β', 'δ']
>>> run_all(lambda x: eq(x, "α") | eq(x, "β") | eq(x, "δ"))
['α', 'β', 'δ']
>>> run_all(lambda x: eq(x, "ω") & eq(x, "ω"))
['ω']
>>> run_all(lambda x: conj(eq(x, "ω"), eq(x, "ω")))
['ω']
```
### The result type and multiple top-level variables
If the fresh-var-receiver provided to an interface has arity 1, results will be single elements. If it has arity > 1, the results will be a tuple of values, each mapping position-wise to the receiver's arguments.
``` python-console
>>> run_all(lambda x, y: eq(x, "foo") & eq(y, "bar") | eq(x, "hello") & eq(y, "world"))
[('foo', 'bar'), ('hello', 'world')]
```
### Defining goal constructors
Calling goal constructors in your top-level program quickly becomes unwieldy. To mitigate this, you can define your own goal constructors.
A goal constructor is a function that takes zero or more arguments, and returns a `Goal` (or some object that implements the `GoalProto`).
A `Goal` is a callable that takes a `State` and returns a `Stream` of `State` objects.
A `Stream` is either:
- empty (`mzero`);
- a callable of no arguments that returns a `Stream` (a thunk); or
- a tuple, `(State, Stream)`.
``` python-console
>>> def likes_pizza(person, out):
... return eq(out, (person, "likes 🍕"))
...
>>> run_all(lambda q: likes_pizza("Jane", q) | likes_pizza("Bill", q))
[('Jane', 'likes 🍕'), ('Bill', 'likes 🍕')]
```
As shown in the above example, it can be convenient to define goals in terms of the combination of other goals. However, if you require access to the current state, you can define the goal returned by your goal constructor explicitly.
``` python
def my_constructor(x):
def _my_constructor(state):
if there_is_something_about(x):
return unit(state)
return mzero
return Goal(_my_constructor)
```
Wrapping your goal with `Goal` means it will be combinable with other goals using `|` and `&`.
#### Recursive goal constructors and `snooze` (Zzz)
If your goal constructor is directly recursive, it will never terminate.
``` python-console
>>> def always_pizza(x):
... return eq(x, "🍕") | always_pizza(x)
...
>>> run(1, lambda x: always_pizza(x))
...
RecursionError: maximum recursion depth exceeded while calling a Python object
```
We provide `snooze` to delay the construction of a goal until it is needed. Using `snooze` we can fix `always_pizza` to return an infinite stream of pizza[^3].
``` python-console
>>> def always_pizza(x):
... return eq(x, "🍕") | snooze(always_pizza, x)
...
>>> rs = irun(lambda x: always_pizza(x))
>>> next(rs)
'🍕'
>>> next(rs)
'🍕'
>>> next(rs)
'🍕'
>>> next(rs)
'🍕'
```
## Developing microkanren
`microkanren` currently requires Python 3.11.
1. `git clone git@github.com:jams2/microkanren.git`
2. `pip install -e .[dev,testing]`
Run the tests with `pytest`.
Format code with `black` and `ruff`:
``` bash
black .
ruff check --fix src tests
```
[^1]: [μKanren: A Minimal Functional Core for Relational Programming (Hemann & Friedman, 2013)](http://webyrd.net/scheme-2013/papers/HemannMuKanren2013.pdf)
[^2]: [cKanren: miniKanren with constraints (Alvis et al, 2011)](http://www.schemeworkshop.org/2011/papers/Alvis2011.pdf)
[^3]: original example `fives` from the μKanren paper altered here to provide more pizza
Raw data
{
"_id": null,
"home_page": "",
"name": "microkanren",
"maintainer": "",
"docs_url": null,
"requires_python": "<3.13,>=3.11",
"maintainer_email": "",
"keywords": "",
"author": "",
"author_email": "Joshua Munn <public@elysee-munn.family>",
"download_url": "https://files.pythonhosted.org/packages/89/af/6e7cd93df4222a3240c5c8adb9cd205d9618636697ec52c93669d58f1e04/microkanren-0.4.4.tar.gz",
"platform": null,
"description": "# microkanren\n\n`microkanren` is an implementation of a miniKanren style relational programming language, embedded in Python. The solver is implemented in the style of \u03bcKanren[^1]. It provides a framework for extending the language with constraints, as well as a basic implementation of disequality and finite domain constraints, in the style of cKanren[^2].\n\nDue to the differences between Python and the reference implementation languages (Scheme, Racket), some divergences from the typical miniKanren API are necessary. It is a goal to capture the spirit of the miniKanren language family, but not the exact API.\n\n* [Installation](#installation)\n* [Usage](#usage)\n + [Basic usage](#basic-usage)\n + [Conjunction and disjunction](#conjunction-and-disjunction)\n + [The result type and multiple top-level variables](#the-result-type-and-multiple-top-level-variables)\n + [Defining goal constructors](#defining-goal-constructors)\n - [Recursive goal constructors and `snooze` (Zzz)](#recursive-goal-constructors-and--snooze---zzz-)\n* [Developing microkanren](#developing-microkanren)\n\n## Installation\n\n``` bash\npip install microkanren\n```\n\n## Usage\n\n### Basic usage\n\nThe basic goal constructor is `eq`. `eq` takes two terms as arguments, and returns a goal that will succeed if the terms can be unified, and fails otherwise.\n\n``` python-console\n>>> from microkanren import eq\n>>> eq(\"\ud83c\udf55\", \"\ud83c\udf55\")\n<microkanren.core.Goal object at 0x7f07d85cced0>\n```\n\nTo run a goal, use one of the provided interfaces: `run`, `run_all`, or `irun`. `run` takes two arguments:\n\n1. an integer, the maximum number of results to return; and\n2. a callable with positional-only arguments, each of which will receive a fresh logic variable.\n\n`run_all` and `irun` take a single argument, the fresh-var-receiver.\n\n``` python-console\n>>> from microkanren import run\n>>> run(1, lambda x: eq(x, \"\ud83c\udf55\"))\n['\ud83c\udf55']\n```\n\nThe return type of `run` and `run_all` is a (possibly-empty) list of results. If the list is empty, there are no solutions that satisfy the goal. `irun` returns a generator that yields single results.\n\n``` python-console\n>>> from microkanren import irun\n>>> rs = irun(lambda x: eq(x, \"\ud83d\ude01\"))\n>>> next(rs)\n'\ud83d\ude01'\n>>> next(rs)\nTraceback (most recent call last):\n File \"<stdin>\", line 1, in <module>\nStopIteration\n```\n\n### Conjunction and disjunction\n\nConjunction and disjunction are provided by the vararg `conj` and `disj` functions. `Goal` objects support combination using `|` and `&` operators, which map to `conj` and `disj`.\n\n``` python-console\n>>> from microkanren import run_all\n>>> run_all(lambda x: disj(eq(x, \"\u03b1\"), eq(x, \"\u03b2\"), eq(x, \"\u03b4\")))\n['\u03b1', '\u03b2', '\u03b4']\n>>> run_all(lambda x: eq(x, \"\u03b1\") | eq(x, \"\u03b2\") | eq(x, \"\u03b4\"))\n['\u03b1', '\u03b2', '\u03b4']\n>>> run_all(lambda x: eq(x, \"\u03c9\") & eq(x, \"\u03c9\"))\n['\u03c9']\n>>> run_all(lambda x: conj(eq(x, \"\u03c9\"), eq(x, \"\u03c9\")))\n['\u03c9']\n```\n\n### The result type and multiple top-level variables\n\nIf the fresh-var-receiver provided to an interface has arity 1, results will be single elements. If it has arity > 1, the results will be a tuple of values, each mapping position-wise to the receiver's arguments.\n\n``` python-console\n>>> run_all(lambda x, y: eq(x, \"foo\") & eq(y, \"bar\") | eq(x, \"hello\") & eq(y, \"world\"))\n[('foo', 'bar'), ('hello', 'world')]\n```\n\n### Defining goal constructors\n\nCalling goal constructors in your top-level program quickly becomes unwieldy. To mitigate this, you can define your own goal constructors.\n\nA goal constructor is a function that takes zero or more arguments, and returns a `Goal` (or some object that implements the `GoalProto`).\n\nA `Goal` is a callable that takes a `State` and returns a `Stream` of `State` objects.\n\nA `Stream` is either:\n- empty (`mzero`);\n- a callable of no arguments that returns a `Stream` (a thunk); or\n- a tuple, `(State, Stream)`.\n\n``` python-console\n>>> def likes_pizza(person, out):\n... return eq(out, (person, \"likes \ud83c\udf55\"))\n...\n>>> run_all(lambda q: likes_pizza(\"Jane\", q) | likes_pizza(\"Bill\", q))\n[('Jane', 'likes \ud83c\udf55'), ('Bill', 'likes \ud83c\udf55')]\n```\n\nAs shown in the above example, it can be convenient to define goals in terms of the combination of other goals. However, if you require access to the current state, you can define the goal returned by your goal constructor explicitly.\n\n``` python\ndef my_constructor(x):\n def _my_constructor(state):\n if there_is_something_about(x):\n return unit(state)\n return mzero\n\n return Goal(_my_constructor)\n```\n\nWrapping your goal with `Goal` means it will be combinable with other goals using `|` and `&`.\n\n#### Recursive goal constructors and `snooze` (Zzz)\n\nIf your goal constructor is directly recursive, it will never terminate.\n\n``` python-console\n>>> def always_pizza(x):\n... return eq(x, \"\ud83c\udf55\") | always_pizza(x)\n...\n>>> run(1, lambda x: always_pizza(x))\n...\nRecursionError: maximum recursion depth exceeded while calling a Python object\n```\n\nWe provide `snooze` to delay the construction of a goal until it is needed. Using `snooze` we can fix `always_pizza` to return an infinite stream of pizza[^3].\n\n``` python-console\n>>> def always_pizza(x):\n... return eq(x, \"\ud83c\udf55\") | snooze(always_pizza, x)\n...\n>>> rs = irun(lambda x: always_pizza(x))\n>>> next(rs)\n'\ud83c\udf55'\n>>> next(rs)\n'\ud83c\udf55'\n>>> next(rs)\n'\ud83c\udf55'\n>>> next(rs)\n'\ud83c\udf55'\n```\n\n## Developing microkanren\n\n`microkanren` currently requires Python 3.11.\n\n1. `git clone git@github.com:jams2/microkanren.git`\n2. `pip install -e .[dev,testing]`\n\nRun the tests with `pytest`.\n\nFormat code with `black` and `ruff`:\n\n``` bash\nblack .\nruff check --fix src tests\n```\n\n[^1]: [\u03bcKanren: A Minimal Functional Core for Relational Programming (Hemann & Friedman, 2013)](http://webyrd.net/scheme-2013/papers/HemannMuKanren2013.pdf)\n[^2]: [cKanren: miniKanren with constraints (Alvis et al, 2011)](http://www.schemeworkshop.org/2011/papers/Alvis2011.pdf)\n[^3]: original example `fives` from the \u03bcKanren paper altered here to provide more pizza\n",
"bugtrack_url": null,
"license": "MIT License Copyright (c) 2023 Joshua Munn Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.",
"summary": "A Python implementation of microkanren extended with constraints",
"version": "0.4.4",
"project_urls": {
"Bug Tracker": "https://github.com/jams2/microkanren/issues",
"Homepage": "https://github.com/jams2/microkanren"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "d07e9a154eec280072aa3a293e39285a5c6d49538a6bba2c110ef68c29c58d03",
"md5": "2006dd3b9cd40eb8f2669928d66cfbaa",
"sha256": "0071990867b4f0e05a7b0c083a665f07d7b1c97e8c7e59782a81c7730da65bef"
},
"downloads": -1,
"filename": "microkanren-0.4.4-py3-none-any.whl",
"has_sig": false,
"md5_digest": "2006dd3b9cd40eb8f2669928d66cfbaa",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<3.13,>=3.11",
"size": 13802,
"upload_time": "2023-10-25T13:47:54",
"upload_time_iso_8601": "2023-10-25T13:47:54.678508Z",
"url": "https://files.pythonhosted.org/packages/d0/7e/9a154eec280072aa3a293e39285a5c6d49538a6bba2c110ef68c29c58d03/microkanren-0.4.4-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "89af6e7cd93df4222a3240c5c8adb9cd205d9618636697ec52c93669d58f1e04",
"md5": "ac7a135f25753f3dc2dce9c87198b489",
"sha256": "bcd84b8471761b5fd2235c1ecb018f3666e1853cd91744ad41190dcd351bddc4"
},
"downloads": -1,
"filename": "microkanren-0.4.4.tar.gz",
"has_sig": false,
"md5_digest": "ac7a135f25753f3dc2dce9c87198b489",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<3.13,>=3.11",
"size": 17148,
"upload_time": "2023-10-25T13:47:56",
"upload_time_iso_8601": "2023-10-25T13:47:56.507460Z",
"url": "https://files.pythonhosted.org/packages/89/af/6e7cd93df4222a3240c5c8adb9cd205d9618636697ec52c93669d58f1e04/microkanren-0.4.4.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-10-25 13:47:56",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "jams2",
"github_project": "microkanren",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"tox": true,
"lcname": "microkanren"
}