{"id":15642117,"url":"https://github.com/suned/stateless","last_synced_at":"2025-08-22T08:31:36.712Z","repository":{"id":205309645,"uuid":"713855925","full_name":"suned/stateless","owner":"suned","description":"Statically typed, purely functional effects for Python.","archived":false,"fork":false,"pushed_at":"2024-05-30T20:13:19.000Z","size":129,"stargazers_count":71,"open_issues_count":5,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-12-07T11:34:05.212Z","etag":null,"topics":["algebraic-effects","functional-programming","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/suned.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-03T11:31:48.000Z","updated_at":"2024-11-28T11:54:09.000Z","dependencies_parsed_at":"2024-01-08T01:05:09.042Z","dependency_job_id":"c209a316-8561-4595-b8f0-f781b52da16d","html_url":"https://github.com/suned/stateless","commit_stats":{"total_commits":47,"total_committers":2,"mean_commits":23.5,"dds":"0.021276595744680882","last_synced_commit":"1045fff9f0c48743e686ef7c433b5139dae419a5"},"previous_names":["suned/stateless"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suned%2Fstateless","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suned%2Fstateless/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suned%2Fstateless/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suned%2Fstateless/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/suned","download_url":"https://codeload.github.com/suned/stateless/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230575851,"owners_count":18247484,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["algebraic-effects","functional-programming","python"],"created_at":"2024-10-03T11:54:37.301Z","updated_at":"2024-12-20T11:08:03.071Z","avatar_url":"https://github.com/suned.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# stateless\n\nStatically typed, purely functional effects for Python.\n\n# Motivation\nProgramming with side-effects is hard: To reason about a unit in your code, like a function, you need to know what the other units in the program are doing to the program state, and understand how that affects what you're trying to achieve.\n\nProgramming without side-effects is _less_ hard: To reason about a unit in you code, like a function, you can focus on what _that_ function is doing, since the units it interacts with don't affect the state of the program in any way.\n\nBut of course side-effects can't be avoided, since what we ultimately care about in programming are just that: The side effects, such as printing to the console or writing to a database.\n\nFunctional effect systems like `stateless` aim to make programming with side-effects less hard. We do this by separating the specification of side-effects from the interpretation, such that functions that need to perform side effects do so indirectly via the effect system.\n\nAs a result, \"business logic\" code never performs side-effects, which makes it easier to reason about, test and re-use.\n\n# Quickstart\n\n```python\nfrom typing import Any, Never\nfrom stateless import Effect, depend, throws, catch, Runtime\n\n\n# stateless.Effect is just an alias for:\n#\n# from typing import Generator, Any\n#\n# type Effect[A, E: Exception, R] = Generator[Type[A] | E, Any, R]\n\n\nclass Files:\n    def read_file(self, path: str) -\u003e str:\n        with open(path) as f:\n            return f.read()\n\n\nclass Console:\n    def print(self, value: Any) -\u003e None:\n        print(value)\n\n\n# Effects are generators that yield \"Abilities\" that can be sent to the\n# generator when an effect is executed. Abilities could be anything, but will often be things that\n# handle side-effects. Here it's a class that can print to the console.\n# In other effects systems, abilities are called \"effect handlers\".\ndef print_(value: Any) -\u003e Effect[Console, Never, None]:\n    console = yield from depend(Console)  # depend returns abilities\n    console.print(value)\n\n\n# Effects can yield exceptions. 'stateless.throws' will catch exceptions\n# for you and yield them to other functions so you can handle them with\n# type safety. The return type of the decorated function in this\n# example is: ´Effect[Files, OSError, str]'\n@throws(OSError)\ndef read_file(path: str) -\u003e Effect[Files, Never, str]:\n    files = yield from depend(Files)\n    return files.read_file(path)\n\n\n# Simple effects can be combined into complex ones by\n# depending on multiple abilities.\ndef print_file(path: str) -\u003e Effect[Files | Console, Never, None]:\n    # catch will return exceptions yielded by other functions\n    result = yield from catch(read_file)(path)\n    match result:\n        case OSError() as error:\n            yield from print_(f\"error: {error}\")\n        case _ as content:\n            yield from print_(content)\n\n\n# Effects are run using `stateless.Runtime.run`.\n# Abilities are provided to effects via 'stateless.Runtime.use'\nruntime = Runtime().use(Console()).use(Files())\nruntime.run(print_file('foo.txt'))\n\n\n```\n\n# Guide\n\n\n## Effects, Abilities and Runtime\n`stateless` is a functional effect system for Python built around a pattern using [generator functions](https://docs.python.org/3/reference/datamodel.html#generator-functions). When programming with `stateless` you will describe your program's side-effects using the `stateless.Effect` type. This is in fact just a type alias for a generator:\n\n\n```python\nfrom typing import Any, Generator, Type\n\n\ntype Effect[A, E: Exception, R] = Generator[Type[A] | E, Any, R]\n```\n In other words, an `Effect` is a generator that can yield classes of type `A` or exceptions of type `E`, can be sent anything, and returns results of type `R`. Let's break that down a bit further:\n\n-  The type variable `A` in `Effect` stands for _\"Ability\"_. This is the type of value that an effect depends on in order to produce its result.\n\n - The type variable `E` parameter of `Effect` stands for _\"Error\"_. This the type of errors that an effect might fail with.\n\n - The type variable `R` stands for _\"Result\"_. This is the type of value that an `Effect` will produce if no errors occur.\n\n We'll see shortly why the _\"send\"_ type of effects must be `Any`, and how `stateless` can still provide good type inference.\n\n\n\nLets start with a very simple example of an `Effect`:\n```python\nfrom typing import Never\n\nfrom stateless import Effect\n\n\ndef hello_world() -\u003e Effect[str, Never, None]:\n    message = yield str\n    print(message)\n```\n\nWhen `hello_world` returns an `Effect[str, Never, None]`, it means that it depends on a `str` instance being sent to produce its value (`A` is parameterized with `str`). It can't fail (`E` is parameterized with `Never`), and it doesn't produce a value (`R` is parameterized with `None`).\n\n`Never` is quite frequently used as the parameter for `E`, so `stateless` also supplies a type alias `Depend` with just that:\n\n```python\nfrom typing import Never\n\nfrom stateless import Effect\n\n\ntype Depend[A, R] = Effect[A, Never, R]\n```\n\nSo `hello_world` could also have been written:\n\n\n```python\nfrom stateless import Depend\n\n\ndef hello_world() -\u003e Depend[str, None]:\n    message = yield str\n    print(message)\n```\n\nTo run an `Effect`, you need an instance of `stateless.Runtime`. `Runtime` has just two methods: `use` and `run`. Let's look at their definitions:\n\n\n```python\nfrom stateless import Effect\n\n\nclass Runtime[A]:\n    def use[A2](self, ability: A2) -\u003e Runtime[A | A2]:\n        ...\n\n    def run[E: Exception, R](self, effect: Effect[A, E, R]) -\u003e R:\n        ...\n```\nThe type parameter `A` of runtime again stands for \"Ability\". This is the type of abilities that this `Runtime` instance can provide.\n\n`Runtime.use` takes an instance of `A`, the ability type, to be sent to the effect passed to `run` upon request (i.e when its type is yielded by the effect).\n\n`Runtime.run` returns the result of running the `Effect` (or raises an exception if the effect fails).\n\nLet's run `hello_world`:\n\n```python\nfrom stateless import Runtime\n\n\nruntime = Runtime().use(b\"Hello, world!\")\nruntime.run(hello_world())  # type-checker error!\n```\nWhoops! We accidentally provided an instance of `bytes` instead of `str`, which was required by `hello_world`. Let's try again:\n\n```python\nfrom stateless import Runtime\n\n\nruntime = Runtime().use(\"Hello, world!\")\nruntime.run(hello_world())  # outputs: Hello, world!\n```\nCool. Okay maybe not. The `hello_world` example is obviously contrived. There's no real benefit to sending `message` to `hello_world` via `yield` over just providing it as a regular function argument. The example is included here just to give you a rough idea of how the different pieces of `stateless` fit together.\n\nOne thing to note is that the `A` type parameter of `Effect` and `Runtime` work together to ensure type safe dependency injection of abilities: You can't forget to provide an ability (or dependency if you will) to an effect without getting a type error. We'll discuss in more detail later when it makes sense to use abilities for dependency injection, and when it makes sense to use regular function arguments.\n\nLet's look at a bigger example. The main point of a purely functional effect system is to enable side-effects such as IO in a purely functional way. So let's implement some abilities for doing side-effects.\n\nWe'll start with an ability we'll call `Console` for writing to the console:\n\n```python\nclass Console:\n    def print(self, line: str) -\u003e None:\n        print(line)\n```\nWe can use `Console` with `Effect` as an ability. Recall that the _\"send\"_ type of `Effect` is `Any`. In order to tell our type checker that the result of yielding the `Console` class will be a `Console` instance, we can use the `stateless.depend` function. Its signature is:\n\n```python\nfrom typing import Type\n\nfrom stateless import Depend\n\n\ndef depend[A](ability: Type[A]) -\u003e Depend[A, A]:\n    ...\n```\n\nSo `depend` just yields the ability type for us, and then returns the instance that will eventually be sent from `Runtime`.\n\nLet's see that in action with the `Console` ability:\n\n```python\nfrom stateless import Depend, depend\n\n\ndef say_hello() -\u003e Depend[Console, None]:\n    console = yield from depend(Console)\n    console.print(f\"Hello, world!\")\n```\n\nYou can of course also just annotate `console` if you prefer:\n\n```python\nfrom stateless import Depend\n\n\ndef say_hello() -\u003e Depend[Console, None]:\n    console: Console = yield Console\n    console.print(f\"Hello, world!\")\n```\n\nLet's add another ability `Files` to read rom the file system:\n\n\n```python\nclass Files:\n    def read(self, path: str) -\u003e str:\n        with open(path, 'r') as f:\n            return f.read()\n```\nPutting it all together:\n\n```python\nfrom stateless import Depend\n\n\ndef print_file(path: str) -\u003e Depend[Console | Files, None]:\n    ...\n```\nNote that `A` is parameterized with `Console | Files` since `print_file` depends on both `Console` and `Files` (i.e it will yield both classes).\n\nLet's add a body for `print_file`:\n\n```python\nfrom stateless import Depend, depend\n\n\ndef print_file(path: str) -\u003e Depend[Console | Files, None]:\n    files = yield from depend(Files)\n    console = yield from depend(Console)\n\n    content = files.read(path)\n    console.print(content)\n```\n\n`print_file` is a good demonstration of why the _\"send\"_ type of `Effect` must be `Any`: Since `print_file` expects to be sent instances of `Console` _or_ `File`, it's not possible for our type-checker to know on which yield which type is going to be sent, and because of the variance of `typing.Generator`, we can't write `depend` in a way that would allow us to type `Effect` with a _\"send\"_ type other than `Any`.\n\n`depend` is a good example of how you can build complex effects using functions that return simpler effects using `yield from`:\n\n\n```python\nfrom stateless import Depend, depend\n\n\ndef get_str() -\u003e Depend[str, str]:\n    s = yield from depend(str)\n    return s\n\n\ndef get_int() -\u003e Depend[str | int, tuple[str, int]]:\n    s = yield from get_str()\n    i = yield from depend(int)\n\n    return (s, i)\n```\n\nIt will often make sense to use an `abc.ABC` as your ability type to enforce programming towards the interface and not the implementation. If you use `mypy` however, note that [using abstract classes where `typing.Type` is expected is a type-error](https://github.com/python/mypy/issues/4717), which will cause problems if you pass an abstract type to `depend`. We recommend disabling this check, which will also likely be the default for `mypy` in the future.\n\nyou can of course run `print_file` with `Runtime`:\n\n\n```python\nfrom stateless import Runtime\n\n\nruntime = Runtime().use(Files()).use(Console())\nruntime.run(print_file('foo.txt'))\n```\nAgain, if we forget to supply an ability for `runtime` required by `print_file`, we'll get a type error.\n\nOf course the main purpose of dependency injection is to vary the injected ability to change the behavior of the effect. For example, we\nmight want to change the behavior of `print_files` in tests:\n\n\n```python\nclass MockConsole(Console):\n    def print(self, line: str) -\u003e None:\n        pass\n\n\nclass MockFiles(Files):\n    def __init__(self, content: str) -\u003e None:\n        self.content = content\n\n    def read(self, path: str) -\u003e str:\n        return self.content\n\n\nconsole: Console = MockConsole()\nfiles: Files = MockFiles('mock content'.)\n\nruntime = Runtime().use(console).use(files)\nruntime.run(print_file('foo.txt'))\n```\n\nOur type-checker will likely infer the types `console` and `files` to be `MockConsole` and `MockFiles` respectively, so we need to annotate them with the super-types `Console` and `Files`. Otherwise it will cause the inferred type of `runtime` to be `Runtime[MockConsole, MockFiles]` which would not be type-safe when calling `run` with an argument of type `Effect[Console | Files, Never, None]` due to the variance of `collections.abc.Generator`.\n\nBesides `Effect` and `Depend`, `stateless` provides you with a few other type aliases that can save you a bit of typing. Firstly success which is just defined as:\n\n\n```python\nfrom typing import Never\n\n\ntype Success[R] = Effect[Never, Never, R]\n```\n\nfor effects that don't fail and don't require abilities (can be easily instantiated using the `stateless.success` function).\n\nSecondly the `Try` type alias, defined as:\n\n\n```python\nfrom typing import Never\n\n\ntype Try[E, R] = Effect[Never, E, R]\n```\nFor effects that do not require abilities, but might fail.\n\nSometimes, instantiating abilities may itself require side-effects. For example, consider a program that requires a `Config` ability:\n\n\n```python\nfrom stateless import Depend\n\n\nclass Config:\n    ...\n\n\ndef main() -\u003e Depend[Config, None]:\n    ...\n```\n\nNow imagine that you want to provide the `Config` ability by reading from environment variables:\n\n\n```python\nimport os\n\nfrom stateless import Depend, depend\n\n\nclass OS:\n    environ: dict[str, str] = os.environ\n\n\ndef get_config() -\u003e Depend[OS, Config]:\n    os = yield from depend(OS)\n    return Config(\n        url=os.environ['AUTH_TOKEN'],\n        auth_token=os.environ['URL']\n    )\n```\n\nTo supply the `Config` instance returned from `get_config`, we can use `Runtime.use_effect`:\n\n\n```python\nfrom stateless import Runtime\n\n\nRuntime().use(OS()).use_effect(get_config()).run(main())\n```\n\n`Runtime.use_effect` assumes that all abilities required by the effect given as its argument can be provided by the runtime. If this is not the case, you'll get a type-checker error:\n\n```python\nfrom stateless import Depend, Runtime\n\n\nclass A:\n    pass\n\n\nclass B:\n    pass\n\n\ndef get_B() -\u003e Depend[A, B]:\n    ...\n\nRuntime().use(A()).use_effect(get_B())  # OK\nRuntime().use_effect(get_B())           # Type-checker error!\n```\n\n## Error Handling\n\nSo far we haven't used the error type `E` for anything: We've simply parameterized it with `typing.Never`. We've claimed that this means that the effect doesn't fail. This is of course not literally true, as exceptions can still occur even if we parameterize `E` with `Never.`\n\nTake the `Files` ability from the previous section for example. Reading from the file system can of course fail for a number of reasons, which in Python will result in a subtype of `OSError` being raised. So calling for example `print_file` might raise an exception:\n\n```python\nfrom stateless import Depend\n\n\ndef f() -\u003e Depend[Files, None]:\n    yield from print_file('doesnt_exist.txt')  # raises FileNotFoundError\n```\nSo what's the point of `E`?\n\nThe point is that programming errors can be grouped into two categories: recoverable errors and unrecoverable errors. Recoverable errors are errors that are expected, and that users of the API we are writing might want to know about. `FileNotFoundError` is an example of such an error.\n\nUnrecoverable errors are errors that there is no point in telling the users of your API about. Depending on the context, `ZeroDivisionError` or `KeyError` might be examples of unrecoverable errors.\n\nThe intended use of `E` is to model recoverable errors so that users of your API can handle them with type safety.\n\nLet's use `E` to model the errors of `Files.read_file`:\n\n\n```python\nfrom stateless import Effect, throw\n\n\ndef read_file(path: str) -\u003e Effect[Files, OSError, str]:\n    files = yield Files\n    try:\n        return files.read_file(path)\n    except OSError as e:\n        return (yield from throw(e))\n```\n\nThe signature of `stateless.throw` is\n\n```python\nfrom typing import Never\n\nfrom stateless import Effect\n\n\ndef throw[E: Exception](e: E) -\u003e Effect[Never, E, Never]:\n    ...\n```\nIn words `throw` returns an effect that just yields `e` and never returns. Because of this signature, if you assign the result of `throw` to a variable, you have to annotate it. But there is no meaningful type\nto annotate it with. So you're better off using the somewhat strange looking syntax `return (yield from throw(e))`.\n\nAt a slightly higher level you can use `stateless.throws` that just catches exceptions and yields them as an effect\n\n```python\nfrom stateless import Depend, throws\n\n\n@throws(OSError)\ndef read_file(path: str) -\u003e Depend[Files, str]:\n    files = yield Files\n    return files.read_file(path)\n\n\nreveal_type(read_file)  # revealed type is: def (str) -\u003e Effect[Files, OSError, str]\n```\n\nError handling in `stateless` is done using the `stateless.catch` decorator. Its signature is:\n\n```python\nfrom stateless import Effect, Depend\n\n\ndef catch[**P, A, E: Exception, R](\n    f: Callable[P, [Effect[A, E, R]]]\n) -\u003e Callable[P, Depend[A, E | R]]:\n    ...\n```\n\nIn words, the `catch` decorator moves the error from the yield type of the `Effect` produced by its argument to the return type of the effect of the function returned from `catch`. This means you can access the potential errors directly in your code:\n\n\n```python\nfrom stateless import Depend\n\n\ndef handle_errors() -\u003e Depend[Files, str]:\n    result: OSError | str = yield from catch(read_file)('foo.txt')\n    match result:\n        case OSError:\n            return 'default value'\n        case str():\n            return result\n\n```\n(You don't need to annotate the type of `result`, it can be inferred by your type checker. We do it here simply because its instructive to look at the types.)\n\nConsequently you can use your type checker to avoid unintentionally unhandled errors, or ignore them with type-safety as you please.\n\n`catch` is a good example of a pattern used in many places in `stateless`: using decorators to change the result of an effect. The reason for this pattern is that generators are mutable objects.\n\nFor example, we could have defined catch like this:\n\n\n```python\ndef bad_catch(effect: Effect[A, E, R]) -\u003e Depend[A, E | R]:\n    ...\n```\n\nBut with this signature, it would not be possible to implement `bad_catch` without  mutating `effect` as a side-effect, since it's necessary to yield from it to implement catching.\n\nIn general, it's not a good idea to write functions that take effects as arguments directly, because it's very easy to accidentally mutate them which would be confusing for the caller:\n\n\n```python\ndef f() -\u003e Depend[str, int]:\n    ...\n\n\ndef dont_do_this(e: Depend[str, int]) -\u003e Depend[str, int]:\n    i = yield from e\n    return i\n\n\ndef this_is_confusing() -\u003e Depend[str, tuple[int, int]]:\n    e = f()\n    r = yield from dont_do_this(e)\n    r_again = yield from e  # e was already exhausted, so 'r_again' is None!\n    return (r, r_again)\n```\nA better idea is to write a decorator that accepts a function that returns effects. That way there is no risk of callers passing generators and then accidentally mutating them as a side effect:\n\n```python\ndef do_this_instead[**P](f: Callable[P, Depend[str, int]]) -\u003e Callable[P, Depend[str, int]]:\n    @wraps(f)\n    def decorator(*args: P.args, **kwargs: P.kwargs) -\u003e Depend[str, int]:\n        i = yield from f(*args, **kwargs)\n        return i\n    return decorator\n\n\ndef this_is_easy():\n    e = f()\n    r = yield from do_this_instead(f)()\n    r_again = yield from e\n    return (r, r_again)\n\n```\n\n## Parallel Effects\nTwo challenges present themselves when running generator based effects in parallel:\n\n- Generators aren't thread-safe.\n- Generators can't be pickled.\n\nHence, instead of sharing effects between threads and processes to run them in parallel, `stateless` gives you tools to share _functions_ that return effects plus _arguments_ to those functions between threads and processes.\n\n`stateless` calls a function that returns an effect plus arguments to pass to that function a _task_, represented by the `stateless.parallel.Task` class.\n\n`stateless` provides two decorators for instantiating `Task` instances: `stateless.parallel.thread` and `stateless.parallel.process`. Their signatures are:\n\n\n```python\nfrom typing import Callable\n\nfrom stateless import Effect\nfrom stateless.parallel import Task\n\n\ndef process[**P, A, E: Exception, R](f: Callable[P, Effect[A, E, R]]) -\u003e Callable[P, Task[A, E, R]]:\n    ...\n\ndef thread[**P, A, E: Exception, R](f: Callable[P, Effect[A, E, R]]) -\u003e Callable[P, Task[A, E, R]]:\n    ...\n```\nDecorating functions with `stateless.parallel.thread` indicate to `stateless` your intention for the resulting task to be run in a separate thread. Decorating functions with `stateless.parallel.process` indicate your intention for the resulting task to be run in a separate process.\n\nBecause of the [GIL](https://en.wikipedia.org/wiki/Global_interpreter_lock), using `stateless.parallel.thread` only makes sense for functions returning effects that are [I/O bound](https://en.wikipedia.org/wiki/I/O_bound). For CPU bound effects, you will want to use `stateless.parallel.process`.\n\nTo run effects in parallel, you use the `stateless.parallel` function. It's signature is roughly:\n\n\n```python\nfrom stateless import Effect\nfrom stateless.parallel import Parallel\n\n\ndef parallel[A, E: Exception, R](*tasks: Task[A, E, R]) -\u003e Effect[A | Parallel, E, tuple[R, ...]]:\n    ...\n```\n(in reality `parallel` is overloaded to correctly union abilities and errors, and reflect the result types of each effect in the result type of the returned effect.)\n\nIn words, `parallel` accepts a variable number of tasks as its argument, and returns a new effect that depends on the `stateless.parallel.Parallel` ability. When executed, the effect returned by `parallel` will run the tasks given as its arguments concurrently.\n\n\nHere is a full example:\n```python\nfrom stateless import parallel, Success, success, Depend\nfrom stateless.parallel import thread, process, Parallel\n\n\ndef sing() -\u003e Success[str]:\n    return success(\"🎵\")\n\n\ndef duet() -\u003e Depend[Parallel, tuple[str, str]]:\n    result = yield from parallel(\n        thread(sing)(),\n        process(sing)()\n    )\n    return result\n```\nWhen using the `Parallel` ability, you must use it as a context manager, because it manages multiple resources to enable concurrent execution of effects:\n```python\nfrom stateless import Runtime\nfrom stateless.parallel import Parallel\n\n\nwith Parallel() as ability:\n    print(Runtime().use(ability).run(duet()))  # outputs: (\"🎵\", \"🎵\")\n```\n\nIn this example the first `sing` invocation will be run in a separate thread because its wrapped with `thread`, and the second `sing` invocation will be run in a separate process because it's wrapped by `process`. Note that although `thread` and `process` are strictly speaking decorators, they don't return `stateless.Effect` instances. For this reason, it's probably not a good idea to use them as `@thread` or `@process`, since this\nreduces the re-usability of the decorated function. Use them at the call site as shown in the example instead.\n\n`stateless.parallel.Task` _does_ however implement `__iter__` to return the result of the decorated function, so you _can_ yield from them if necessary:\n\n```python\nfrom stateless import Success, thread\n\n\ndef sing_more() -\u003e Success[str]:\n    # This is rather pointless,\n    # but helps you out if you for some\n    # reason have used @thread instead of thread(...)\n    note = yield from thread(sing)()\n    return note * 2\n```\nIf you need more control over the resources managed by `stateless.parallel.Parallel`, you can pass them as arguments:\n```python\nfrom multiprocessing.pool import ThreadPool\nfrom multiprocessing import Manager\n\nfrom stateless.parallel import Parallel\n\n\nwith (\n    Manager() as manager,\n    manager.Pool() as pool,\n    ThreadPool() as thread_pool,\n    Parallel(thread_pool, pool) as parallel\n):\n    ...\n```\nThe process pool used to execute `stateless.parallel.Task` instances needs to be run with a manager because it needs to be sent to the process executing the task in case it needs to run more\neffects in other processes.\n\nNote that if you pass in in the thread pool and proxy pool as arguments, `stateless.parallel.Parallel` will not exit them for you when it itself exits: you need to manage their state yourself.\n\n\nYou can of course subclass `stateless.parallel.Parallel` to change the interpretation of this ability (for example in tests). The two main functions you'll want to override is `run_thread_tasks` and `run_cpu_tasks`:\n\n```python\nfrom stateless import Runtime, Effect\nfrom stateless.parallel import Parallel, Task\n\n\nclass MockParallel(Parallel):\n    def __init__(self):\n        pass\n\n    def run_cpu_tasks(self,\n                 runtime: Runtime[object],\n                 tasks: Sequence[Task[object, Exception, object]]) -\u003e Tuple[object, ...]:\n        return tuple(runtime.run(iter(task)) for task in tasks)\n\n    def run_thread_tasks(self\n                    runtime: Runtime[object],\n                    effects: Sequence[Effect[object, Exception, object]]) -\u003e Tuple[object, ...]:\n        return tuple(runtime.run(iter(task)) for task in tasks)\n```\n## Repeating and Retrying Effects\n\nA `stateless.Schedule` is a type with an `__iter__` method that returns an effect producing an iterator of `timedelta` instances. It's defined like:\n\n```python\nfrom typing import Protocol, Iterator\nfrom datetime import timedelta\n\nfrom stateless import Depend\n\n\nclass Schedule[A](Protocol):\n    def __iter__(self) -\u003e Depend[A, Iterator[timedelta]]:\n        ...\n```\nThe type parameter `A` is present because some schedules may require abilities to complete.\n\nThe `stateless.schedule` module contains a number of of helpful implemenations of `Schedule`, for example `Spaced` or `Recurs`.\n\nSchedules can be used with the `repeat` decorator, which takes schedule as its first argument and repeats the decorated function returning an effect until the schedule is exhausted or an error occurs:\n\n```python\nfrom datetime import timedelta\n\nfrom stateless import repeat, success, Success, Runtime\nfrom stateless.schedule import Recurs, Spaced\nfrom stateless.time import Time\n\n\n@repeat(Recurs(2, Spaced(timedelta(seconds=2))))\ndef f() -\u003e Success[str]:\n    return success(\"hi!\")\n\n\nprint(Runtime().use(Time()).run(f()))  # outputs: (\"hi!\", \"hi!\")\n```\nEffects created through repeat depends on the `Time` ability from `stateless.time` because it needs to sleep between each execution of the effect.\n\nSchedules are a good example of a pattern used a lot in `stateless`: Classes with an `__iter__` method that returns effects.\n\nThis is a useful pattern because such objects can be yielded from in functions returning effects multiple times where a new generator will be instantiated every time:\n\n```python\ndef this_works() -\u003e Success[timedelta]:\n    schedule = Spaced(timedelta(seconds=2))\n    deltas = yield from schedule\n    deltas_again = yield from schedule  # safe!\n    return deltas\n```\n\nFor example, `repeat` needs to yield from the schedule given as its argument to repeat the decorated function. If the schedule was just a generator it would only be possible to yield from the schedule the first time `f` in this example was called.\n\n`stateless.retry` is like `repeat`, except that it returns succesfully\nwhen the decorated function yields no errors, or fails when the schedule is exhausted:\n\n```python\nfrom datetime import timedelta\n\nfrom stateless import retry, throw, Try, throw, success, Runtime\nfrom stateless.schedule import Recurs, Spaced\nfrom stateless.time import Time\n\n\nfail = True\n\n\n@retry(Recurs(2, Spaced(timedelta(seconds=2))))\ndef f() -\u003e Try[RuntimeError, str]:\n    global fail\n    if fail:\n        fail = False\n        return throw(RuntimeError('Whoops...'))\n    else:\n        return success('Hooray!')\n\n\nprint(Runtime().use(Time()).run(f()))  # outputs: 'Hooray!'\n```\n\n## Memoization\n\nEffects can be memoized using the `stateless.memoize` decorator:\n\n\n```python\nfrom stateless import memoize, Depend\nfrom stateless.console import Console, print_line\n\n\n@memoize\ndef f() -\u003e Depend[Console, str]:\n    yield from print_line('f was called')\n    return 'done'\n\n\ndef g() -\u003e Depend[Console, tuple[str, str]]:\n    first = yield from f()\n    second = yield from f()\n    return first, second\n\n\nresult = Runtime().use(Console()).run(f())  # outputs: 'f was called' once, even though the effect was yielded twice\n\nprint(result)  # outputs: ('done', 'done')\n```\n`memoize` works like [`functools.lru_cache`](https://docs.python.org/3/library/functools.html#functools.lru_cache), in that the memoized effect\nis cached based on the arguments of the decorated function. In fact, `memoize` takes the same parameters as `functools.lru_cache` (`maxsize` and `typed`) with the same meaning.\n# Known Issues\n\nSee the [issues](https://github.com/suned/stateless/issues) page.\n\n# Algebraic Effects Vs. Monads\n\nAll functional effect system work essentially the same way:\n\n1. Programs send a description of the side-effect needed to be performed to the effect system and pause their executing while the effect system handles the side-effect.\n2. Once the result of performing the side-effect is ready, program execution is resumed at the point it was paused\n\nStep 2. is the tricky part: how can program execution be resumed at the point it was paused?\n\n[Monads](https://en.wikipedia.org/wiki/Monad_(functional_programming)) are the most common solution. When programming with monads, in addition to supplying the effect system with a description of a side-effect, the programmer also supplies a function to\nbe called with the result of handling the described effect. In functional programming such a function is called a _continuation_. In other paradigms it might be called a _callback function_.\n\nFor example it might look like this:\n\n```python\ndef say_hello() -\u003e IO[None]:\n    return Input(\"whats your name?\").bind(lambda name: Print(f\"Hello, {name}!\"))\n```\nOne of the main benefits of basing effect systems on monads is that they don't rely on any special language features: its all literally just functions.\n\nHowever, many programmers find monads awkward. Programming with callback functions often lead to code thats hard for humans to parse, which has ultimately inspired specialized language features for hiding the callback functions with syntax sugar like [Haskell's do notation](https://en.wikibooks.org/wiki/Haskell/do_notation), or [for comprehensions in Scala](https://docs.scala-lang.org/tour/for-comprehensions.html).\n\nMoreover, monads famously do not compose, meaning that when writing code that needs to juggle multiple types of side-effects (like errors and IO), it's up to the programmer to pack and unpack results of various types of effects (or use advanced features like [monad transformers](https://en.wikibooks.org/wiki/Haskell/Monad_transformers) which come with their own set of problems).\n\nAdditionally, in languages with dynamic binding such as Python, calling functions is relatively expensive, which means that using callbacks as the principal method for resuming computation comes with a fair amount of performance overhead.\n\nFinally, interpreting monads is often a recursive procedure, meaning that it's necessary to worry about stack safety in languages without tail call optimisation such as Python. This is usually solved using [trampolines](https://en.wikipedia.org/wiki/Trampoline_(computing)) which further adds to the performance overhead.\n\n\nBecause of all these practical challenges of programming with monads, people have been looking for alternatives. Algebraic effects is one the things suggested that address many of the challenges of monadic effect systems.\n\nIn algebraic effect systems, such as `stateless`, the programmer still supplies the effect system with a description of the side-effect to be carried out, but instead of supplying a callback function to resume the\ncomputation with, the result of handling the effect is returned to the point in program execution that the effect description was produced. The main drawback of this approach is that it requires special language features to do this. In Python however, such a language feature _does_ exist: Generators and coroutines.\n\nUsing coroutines for algebraic effects solves many of the challenges with monadic effect systems:\n\n- No callback functions are required, so readability and understandability of the effectful code is much more straightforward.\n- Code that needs to describe side-effects can simply list all the effects it requires, so there is no composition problem.\n- There are no callback functions, so no need to worry about performance overhead of calling a large number of functions or using trampolines to ensure stack safety.\n\n\n# Background\n\n- [Do be do be do (Lindley, McBride and McLaughlin)](https://arxiv.org/pdf/1611.09259.pdf)\n\n- [Handlers of Algebraic Effects (Plotkin and Pretnar)](https://homepages.inf.ed.ac.uk/gdp/publications/Effect_Handlers.pdf)\n\n- [One-Shot Algebraic Effects as Coroutines (Kawahara and Kameyama)](https://link.springer.com/chapter/10.1007/978-3-030-57761-2_8) (with an implementation in [ruby](https://github.com/nymphium/ruff) and [lua](https://github.com/Nymphium/eff.lua))\n\n# Similar Projects\n\n- [Abilities in the Unison language](https://www.unison-lang.org/)\n\n- [Effects in OCaml 5.0](https://v2.ocaml.org/manual/effects.html)\n\n- [Frank language](https://github.com/frank-lang/frank)\n\n- [Koka language](https://koka-lang.github.io/koka/doc/index.html)\n\n- [Eff language](https://www.eff-lang.org/)\n\n- [Effekt language](https://effekt-lang.org/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuned%2Fstateless","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsuned%2Fstateless","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuned%2Fstateless/lists"}