# darkcore
**darkcore** is a lightweight functional programming toolkit for Python.
It brings **Functor / Applicative / Monad** abstractions, classic monads like **Maybe, Either/Result, Reader, Writer, State**,
and an expressive **operator DSL** (`|`, `>>`, `@`) that makes Python feel almost like Haskell.
---
## ✨ Features
- Functor / Applicative / Monad base abstractions
- Core monads implemented:
- `Maybe` — handle missing values
- `Either` / `Result` — safe error handling
- `Validation` — accumulate multiple errors
- `Reader` — dependency injection / environment
- `Writer` — accumulate logs
- `State` — stateful computations
- Monad transformers: `MaybeT`, `ResultT`, `ReaderT`, `StateT`, `WriterT`
- Utilities: `traverse`/`sequence`, Applicative combinators
- Advanced monads: `RWST` (Reader-Writer-State)
- Operator overloads for concise DSL-style code:
- `|` → `fmap` (map)
- `>>` → `bind` (flatMap)
- `@` → `ap` (applicative apply)
- High test coverage, Monad law tests included
---
## 🚀 Installation
```bash
pip install darkcore
```
(or use Poetry)
---
## 🧪 Quick Examples
### Maybe
```python
from darkcore.maybe import Maybe
m = Maybe(3) | (lambda x: x+1) >> (lambda y: Maybe(y*2))
print(m) # Just(8)
n = Maybe(None) | (lambda x: x+1)
print(n) # Nothing
```
---
### Result
```python
from darkcore.result import Ok, Err
def parse_int(s: str):
try:
return Ok(int(s))
except ValueError:
return Err(f"invalid int: {s}")
res = parse_int("42") >> (lambda x: Ok(x * 2))
print(res) # Ok(84)
res2 = parse_int("foo") >> (lambda x: Ok(x * 2))
print(res2) # Err("invalid int: foo")
```
---
### Validation: accumulate errors via Applicative
```python
from darkcore.validation import Success, Failure
def positive(x: int):
return Failure(["non-positive"]) if x <= 0 else Success(x)
v = Success(lambda a: lambda b: a + b).ap(positive(-1)).ap(positive(0))
print(v) # Failure(['non-positive', 'non-positive'])
# Result would stop at the first failure
```
Validation is primarily intended for Applicative composition; `bind` short-circuits like `Result` and is not recommended for error accumulation scenarios.
---
### Reader
```python
from darkcore.reader import Reader
get_user = Reader(lambda env: env["user"])
greet = get_user | (lambda u: f"Hello {u}")
print(greet.run({"user": "Alice"})) # "Hello Alice"
```
---
### Writer
```python
from darkcore.writer import Writer
# list log by default
w = Writer.pure(3).tell(["start"]) >> (lambda x: Writer(x + 1, ["inc"]))
print(w) # Writer(4, log=['start', 'inc'])
# for non-``list`` logs, pass ``empty`` and ``combine`` explicitly
# ``empty`` provides the identity element and ``combine`` appends logs
w2 = Writer("hi", empty=str, combine=str.__add__).tell("!")
print(w2) # Writer('hi', log='!')
# omitting these for a non-``list`` log raises ``TypeError``
try:
Writer("hi", "!") # missing empty/combine
except TypeError:
print("expected TypeError")
```
---
### State
```python
from darkcore.state import State
inc = State(lambda s: (s, s+1))
prog = inc >> (lambda x: State(lambda s: (x+s, s)))
print(prog.run(1)) # (3, 2)
```
### Traverse utilities
```python
from darkcore.traverse import traverse_result
from darkcore.result import Ok, Err
def parse_int(s: str):
try:
return Ok(int(s))
except ValueError:
return Err(f"bad: {s}")
print(traverse_result(["1", "2"], parse_int)) # Ok([1, 2])
print(traverse_result(["1", "x"], parse_int)) # Err("bad: x")
```
`Result` short-circuits on the first `Err` in `traverse_*` / `sequence_*`, whereas `Validation` accumulates errors under Applicative composition.
### RWST
```python
from darkcore.rwst import RWST
from darkcore.result import Ok
combine = lambda a, b: a + b
action = RWST.ask(Ok.pure, combine=combine, empty=list).bind(
lambda env: RWST.tell([env], Ok.pure, combine=combine, empty=list)
)
print(action(1, 0)) # Ok(((None, 0), [1]))
```
### Operator DSL
```python
from darkcore.maybe import Maybe
mf = Maybe(lambda x: x * 2)
mx = Maybe(4)
print((mf @ mx) | (lambda x: x + 1)) # Just(9)
```
---
## 📖 Integration Example
```python
from darkcore.reader import Reader
from darkcore.writer import Writer
from darkcore.state import State
from darkcore.result import Ok, Err
# Reader: get user from environment
get_user = Reader(lambda env: env.get("user"))
# Result: validate existence
to_result = lambda user: Err("no user") if user is None else Ok(user)
# Writer: log user
log_user = lambda user: Writer(user, [f"got user={user}"])
# State: update counter
update_state = lambda user: State(lambda s: (f"{user}@{s}", s+1))
env = {"user": "alice"}
user = get_user.run(env)
res = to_result(user) >> (lambda u: Ok(log_user(u)))
writer = res.value
print(writer.log) # ['got user=alice']
out, s2 = update_state(writer.value).run(42)
print(out, s2) # alice@42 43
```
---
## Why?
- **Safer business code**
- Avoid nested `try/except` and `if None` checks
- Express computations declaratively with monads
- **Educational value**
- Learn Haskell/FP concepts hands-on in Python
- **Expressive DSL**
- `|`, `>>`, `@` make pipelines concise and clear
---
## Development
```bash
git clone https://github.com/minamorl/darkcore
cd darkcore
poetry install
poetry run pytest -v --cov=darkcore
```
---
## License
MIT
Raw data
{
"_id": null,
"home_page": "https://github.com/minamorl/darkcore",
"name": "darkcore",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.12",
"maintainer_email": null,
"keywords": "monad, functional, dsl, business logic",
"author": "minamorl",
"author_email": "minamorl@users.noreply.github.com",
"download_url": "https://files.pythonhosted.org/packages/78/8b/3f4cadd10e26546948ef0d8e3a5576030b4d52f70fa938953a3c9d9b4de9/darkcore-0.4.0.tar.gz",
"platform": null,
"description": "# darkcore\n\n**darkcore** is a lightweight functional programming toolkit for Python. \nIt brings **Functor / Applicative / Monad** abstractions, classic monads like **Maybe, Either/Result, Reader, Writer, State**, \nand an expressive **operator DSL** (`|`, `>>`, `@`) that makes Python feel almost like Haskell.\n\n---\n\n## \u2728 Features\n\n- Functor / Applicative / Monad base abstractions\n- Core monads implemented:\n - `Maybe` \u2014 handle missing values\n - `Either` / `Result` \u2014 safe error handling\n - `Validation` \u2014 accumulate multiple errors\n - `Reader` \u2014 dependency injection / environment\n - `Writer` \u2014 accumulate logs\n - `State` \u2014 stateful computations\n- Monad transformers: `MaybeT`, `ResultT`, `ReaderT`, `StateT`, `WriterT`\n- Utilities: `traverse`/`sequence`, Applicative combinators\n- Advanced monads: `RWST` (Reader-Writer-State)\n- Operator overloads for concise DSL-style code:\n - `|` \u2192 `fmap` (map)\n - `>>` \u2192 `bind` (flatMap)\n - `@` \u2192 `ap` (applicative apply)\n- High test coverage, Monad law tests included\n\n---\n\n## \ud83d\ude80 Installation\n\n```bash\npip install darkcore\n```\n\n(or use Poetry)\n\n---\n\n## \ud83e\uddea Quick Examples\n\n### Maybe\n\n```python\nfrom darkcore.maybe import Maybe\n\nm = Maybe(3) | (lambda x: x+1) >> (lambda y: Maybe(y*2))\nprint(m) # Just(8)\n\nn = Maybe(None) | (lambda x: x+1)\nprint(n) # Nothing\n```\n\n---\n\n### Result\n\n```python\nfrom darkcore.result import Ok, Err\n\ndef parse_int(s: str):\n try:\n return Ok(int(s))\n except ValueError:\n return Err(f\"invalid int: {s}\")\n\nres = parse_int(\"42\") >> (lambda x: Ok(x * 2))\nprint(res) # Ok(84)\n\nres2 = parse_int(\"foo\") >> (lambda x: Ok(x * 2))\nprint(res2) # Err(\"invalid int: foo\")\n```\n\n---\n\n### Validation: accumulate errors via Applicative\n\n```python\nfrom darkcore.validation import Success, Failure\n\ndef positive(x: int):\n return Failure([\"non-positive\"]) if x <= 0 else Success(x)\n\nv = Success(lambda a: lambda b: a + b).ap(positive(-1)).ap(positive(0))\nprint(v) # Failure(['non-positive', 'non-positive'])\n\n# Result would stop at the first failure\n```\n\nValidation is primarily intended for Applicative composition; `bind` short-circuits like `Result` and is not recommended for error accumulation scenarios.\n\n---\n\n### Reader\n\n```python\nfrom darkcore.reader import Reader\n\nget_user = Reader(lambda env: env[\"user\"])\ngreet = get_user | (lambda u: f\"Hello {u}\")\n\nprint(greet.run({\"user\": \"Alice\"})) # \"Hello Alice\"\n```\n\n---\n\n### Writer\n\n```python\nfrom darkcore.writer import Writer\n\n# list log by default\nw = Writer.pure(3).tell([\"start\"]) >> (lambda x: Writer(x + 1, [\"inc\"]))\nprint(w) # Writer(4, log=['start', 'inc'])\n\n# for non-``list`` logs, pass ``empty`` and ``combine`` explicitly\n# ``empty`` provides the identity element and ``combine`` appends logs\nw2 = Writer(\"hi\", empty=str, combine=str.__add__).tell(\"!\")\nprint(w2) # Writer('hi', log='!')\n\n# omitting these for a non-``list`` log raises ``TypeError``\ntry:\n Writer(\"hi\", \"!\") # missing empty/combine\nexcept TypeError:\n print(\"expected TypeError\")\n```\n\n---\n\n### State\n\n```python\nfrom darkcore.state import State\n\ninc = State(lambda s: (s, s+1))\nprog = inc >> (lambda x: State(lambda s: (x+s, s)))\n\nprint(prog.run(1)) # (3, 2)\n```\n\n### Traverse utilities\n\n```python\nfrom darkcore.traverse import traverse_result\nfrom darkcore.result import Ok, Err\n\ndef parse_int(s: str):\n try:\n return Ok(int(s))\n except ValueError:\n return Err(f\"bad: {s}\")\n\nprint(traverse_result([\"1\", \"2\"], parse_int)) # Ok([1, 2])\nprint(traverse_result([\"1\", \"x\"], parse_int)) # Err(\"bad: x\")\n```\n\n`Result` short-circuits on the first `Err` in `traverse_*` / `sequence_*`, whereas `Validation` accumulates errors under Applicative composition.\n\n### RWST\n\n```python\nfrom darkcore.rwst import RWST\nfrom darkcore.result import Ok\n\ncombine = lambda a, b: a + b\n\naction = RWST.ask(Ok.pure, combine=combine, empty=list).bind(\n lambda env: RWST.tell([env], Ok.pure, combine=combine, empty=list)\n)\n\nprint(action(1, 0)) # Ok(((None, 0), [1]))\n```\n\n### Operator DSL\n\n```python\nfrom darkcore.maybe import Maybe\n\nmf = Maybe(lambda x: x * 2)\nmx = Maybe(4)\nprint((mf @ mx) | (lambda x: x + 1)) # Just(9)\n```\n\n---\n\n## \ud83d\udcd6 Integration Example\n\n```python\nfrom darkcore.reader import Reader\nfrom darkcore.writer import Writer\nfrom darkcore.state import State\nfrom darkcore.result import Ok, Err\n\n# Reader: get user from environment\nget_user = Reader(lambda env: env.get(\"user\"))\n\n# Result: validate existence\nto_result = lambda user: Err(\"no user\") if user is None else Ok(user)\n\n# Writer: log user\nlog_user = lambda user: Writer(user, [f\"got user={user}\"])\n\n# State: update counter\nupdate_state = lambda user: State(lambda s: (f\"{user}@{s}\", s+1))\n\nenv = {\"user\": \"alice\"}\n\nuser = get_user.run(env)\nres = to_result(user) >> (lambda u: Ok(log_user(u)))\nwriter = res.value\nprint(writer.log) # ['got user=alice']\n\nout, s2 = update_state(writer.value).run(42)\nprint(out, s2) # alice@42 43\n```\n\n---\n\n## Why?\n\n- **Safer business code** \n - Avoid nested `try/except` and `if None` checks \n - Express computations declaratively with monads \n- **Educational value** \n - Learn Haskell/FP concepts hands-on in Python \n- **Expressive DSL** \n - `|`, `>>`, `@` make pipelines concise and clear \n\n---\n\n## Development\n\n```bash\ngit clone https://github.com/minamorl/darkcore\ncd darkcore\npoetry install\npoetry run pytest -v --cov=darkcore\n```\n\n---\n\n## License\n\nMIT\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Practical functional programming primitives for Python: Monads, Transformers, and DSL operators for safe business logic",
"version": "0.4.0",
"project_urls": {
"Homepage": "https://github.com/minamorl/darkcore",
"Repository": "https://github.com/minamorl/darkcore"
},
"split_keywords": [
"monad",
" functional",
" dsl",
" business logic"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "0c428d23d65f5a43bb5a34d82d890e037018328a326223028275fdac9348e9af",
"md5": "41a3af5607bd67b06ba5d45bd041a654",
"sha256": "a16f4edffc50f2518fd4e562931212bf534cfddbcd153331d3536965b42d3242"
},
"downloads": -1,
"filename": "darkcore-0.4.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "41a3af5607bd67b06ba5d45bd041a654",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.12",
"size": 19053,
"upload_time": "2025-08-22T19:40:57",
"upload_time_iso_8601": "2025-08-22T19:40:57.467141Z",
"url": "https://files.pythonhosted.org/packages/0c/42/8d23d65f5a43bb5a34d82d890e037018328a326223028275fdac9348e9af/darkcore-0.4.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "788b3f4cadd10e26546948ef0d8e3a5576030b4d52f70fa938953a3c9d9b4de9",
"md5": "e480f6c48ae4c0c109fb15baff499dba",
"sha256": "2110ef35b4d73ca78e8aeff0235684993c3521b3fb983b36780ae5ad1c67d871"
},
"downloads": -1,
"filename": "darkcore-0.4.0.tar.gz",
"has_sig": false,
"md5_digest": "e480f6c48ae4c0c109fb15baff499dba",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.12",
"size": 13383,
"upload_time": "2025-08-22T19:40:58",
"upload_time_iso_8601": "2025-08-22T19:40:58.853563Z",
"url": "https://files.pythonhosted.org/packages/78/8b/3f4cadd10e26546948ef0d8e3a5576030b4d52f70fa938953a3c9d9b4de9/darkcore-0.4.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-22 19:40:58",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "minamorl",
"github_project": "darkcore",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "darkcore"
}