{"id":15060932,"url":"https://github.com/dbrattli/expression","last_synced_at":"2025-05-15T10:07:41.621Z","repository":{"id":37900582,"uuid":"298085065","full_name":"dbrattli/Expression","owner":"dbrattli","description":"Functional programming for Python","archived":false,"fork":false,"pushed_at":"2025-03-11T22:18:47.000Z","size":2462,"stargazers_count":632,"open_issues_count":24,"forks_count":34,"subscribers_count":34,"default_branch":"main","last_synced_at":"2025-05-15T10:07:02.908Z","etag":null,"topics":["fsharp","functional-programming","oslash","python","railway-oriented-programming"],"latest_commit_sha":null,"homepage":"https://expression.readthedocs.io","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/dbrattli.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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":"2020-09-23T20:20:48.000Z","updated_at":"2025-05-13T06:20:21.000Z","dependencies_parsed_at":"2023-09-24T18:11:43.390Z","dependency_job_id":"8d58d90b-b521-4df5-aa2e-cdd288e87f49","html_url":"https://github.com/dbrattli/Expression","commit_stats":{"total_commits":417,"total_committers":18,"mean_commits":"23.166666666666668","dds":0.2086330935251799,"last_synced_commit":"efd6d00b78a315af8ca5bbf3076324d23d787f4d"},"previous_names":["dbrattli/fslash","dbrattli/expression","cognitedata/expression"],"tags_count":85,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbrattli%2FExpression","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbrattli%2FExpression/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbrattli%2FExpression/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbrattli%2FExpression/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dbrattli","download_url":"https://codeload.github.com/dbrattli/Expression/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254319721,"owners_count":22051074,"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":["fsharp","functional-programming","oslash","python","railway-oriented-programming"],"created_at":"2024-09-24T23:06:48.383Z","updated_at":"2025-05-15T10:07:36.611Z","avatar_url":"https://github.com/dbrattli.png","language":"Python","readme":"\n# Expression\n\n[![PyPI](https://img.shields.io/pypi/v/expression.svg)](https://pypi.python.org/pypi/Expression)\n![Python package](https://github.com/cognitedata/expression/workflows/Python%20package/badge.svg)\n[![Publish Package](https://github.com/dbrattli/Expression/actions/workflows/python-publish.yml/badge.svg)](https://github.com/dbrattli/Expression/actions/workflows/python-publish.yml)\n[![Documentation Status](https://readthedocs.org/projects/expression/badge/?version=latest)](https://expression.readthedocs.io/en/latest/?badge=latest)\n[![codecov](https://codecov.io/gh/cognitedata/expression/branch/main/graph/badge.svg)](https://codecov.io/gh/cognitedata/expression)\n\n\u003e Pragmatic functional programming\n\n\nExpression aims to be a solid, type-safe, pragmatic, and high performance library for\nfrictionless and practical functional programming in Python 3.10+.\n\nBy pragmatic, we mean that the goal of the library is to use simple abstractions to\nenable you to do practical and productive functional programming in Python (instead of\nbeing a [Monad tutorial](https://github.com/dbrattli/OSlash)).\n\nPython is a multi-paradigm programming language that also supports functional\nprogramming constructs such as functions, higher-order functions, lambdas, and in many\nways favors composition over inheritance.\n\n\u003e Better Python with F#\n\nExpression tries to make a better Python by providing several functional features\ninspired by [F#](https://fsharp.org). This serves several purposes:\n\n- Enable functional programming in a Pythonic way, i.e., make sure we are not\n  over-abstracting things. Expression will not require purely functional programming as\n  would a language like Haskell.\n- Everything you learn with Expression can also be used with F#. Learn F# by starting in\n  a programming language they already know. Perhaps get inspired to also [try out\n  F#](https://aka.ms/fsharphome) by itself.\n- Make it easier for F# developers to use Python when needed, and re-use many of the\n  concepts and abstractions they already know and love.\n\nExpression will enable you to work with Python using many of the same programming\nconcepts and abstractions. This enables concepts such as [Railway oriented\nprogramming](https://fsharpforfunandprofit.com/rop/) (ROP) for better and predictable\nerror handling. Pipelining for workflows, computational expressions, etc.\n\n\u003e _Expressions evaluate to a value. Statements do something._\n\nF# is a functional programming language for .NET that is succinct (concise, readable,\nand type-safe) and kind of [Pythonic](https://docs.python.org/3/glossary.html). F# is in\nmany ways very similar to Python, but F# can also do a lot of things better than Python:\n\n- Strongly typed, if it compiles it usually works making refactoring much safer. You can\n  trust the type-system. With [mypy](http://mypy-lang.org/) or\n  [Pylance](https://github.com/microsoft/pylance-release) you often wonder who is right\n  and who is wrong.\n- Type inference, the compiler deduces types during compilation\n- Expression based language\n\n\n## Getting Started\n\nYou can install the latest `expression` from PyPI by running `pip` (or\n`pip3`). Note that `expression` only works for Python 3.10+.\n\n```console\n\u003e pip install expression\n```\n\nTo add Pydantic v2 support, install the `pydantic` extra:\n\n```console\n\u003e pip install expression[pydantic]\n```\n\n\n## Goals\n\n- Industrial strength library for functional programming in Python.\n- The resulting code should look and feel like Python\n  ([PEP-8](https://www.python.org/dev/peps/pep-0008/)). We want to make a\n  better Python, not some obscure DSL or academic Monad tutorial.\n- Provide pipelining and pipe friendly methods. Compose all the things!\n- Dot-chaining on objects as an alternative syntax to pipes.\n- Lower the cognitive load on the programmer by:\n  - Avoid currying, not supported in Python by default and not a well known\n    concept by Python programmers.\n  - Avoid operator (`|`, `\u003e\u003e`, etc) overloading, this usually confuses more\n    than it helps.\n  - Avoid recursion. Recursion is not normally used in Python and any use of it\n    should be hidden within the SDK.\n- Provide [type-hints](https://docs.python.org/3/library/typing.html) for all\n  functions and methods.\n- Support PEP 634 and structural pattern matching.\n- Code must pass strict static type checking by\n  [Pylance](https://devblogs.microsoft.com/python/announcing-pylance-fast-feature-rich-language-support-for-python-in-visual-studio-code/).\n  Pylance is awesome, use it!\n- [Pydantic](https://pydantic-docs.helpmanual.io/) friendly data types. Use Expression\n  types as part of your Pydantic data model and (de)serialize to/from JSON.\n\n\n## Supported features\n\nExpression will never provide you with all the features of F# and .NET. We are\nproviding a few of the features we think are useful, and will add more\non-demand as we go along.\n\n- **Pipelining** - for creating workflows.\n- **Composition** - for composing and creating new operators.\n- **Fluent or Functional** syntax, i.e., dot chain or pipeline operators.\n- **Pattern Matching** - an alternative flow control to `if-elif-else`.\n- **Error Handling** - Several error handling types.\n  - **Option** - for optional stuff and better `None` handling.\n  - **Result** - for better error handling and enables railway-oriented\n    programming in Python.\n  - **Try** - a simpler result type that pins the error to an Exception.\n- **Collections** - immutable collections.\n  - **TypedArray** - a generic array type that abstracts the details of\n    `bytearray`, `array.array` and `list` modules.\n  - **Sequence** - a better\n    [itertools](https://docs.python.org/3/library/itertools.html) and\n    fully compatible with Python iterables.\n  - **Block** - a frozen and immutable list type.\n  - **Map** - a frozen and immutable dictionary type.\n  - **AsyncSeq** - Asynchronous iterables.\n  - **AsyncObservable** - Asynchronous observables. Provided separately\n    by [aioreactive](https://github.com/dbrattli/aioreactive).\n- **Data Modeling** - sum and product types\n  - **@tagged_union** - A tagged (discriminated) union type decorator.\n- **Parser Combinators** - A recursive decent string parser combinator\n  library.\n- **Effects**: - lightweight computational expressions for Python. This\n  is amazing stuff.\n  - **option** - an optional world for working with optional values.\n  - **result** - an error handling world for working with result values.\n  - **seq** - a world for working with sequences.\n  - **async_result** - an asynchronous error handling world for working\n    with asynchronous result values.\n  - **async_option** - an asynchronous optional world for working with\n    asynchronous optional values.\n- **Mailbox Processor**: for lock free programming using the [Actor\n  model](https://en.wikipedia.org/wiki/Actor_model).\n- **Cancellation Token**: for cancellation of asynchronous (and\n  synchronous) workflows.\n- **Disposable**: For resource management.\n\n\n### Pipelining\n\nExpression provides a `pipe` function similar to `|\u003e` in F#. We don't want to overload\nany Python operators, e.g., `|` so `pipe` is a plain old function taking N-arguments,\nand will let you pipe a value through any number of functions.\n\n```python\nfrom collections.abc import Callable\n\nfrom expression import pipe\n\n\nv = 1\nfn1: Callable[[int], int] = lambda x: x + 1\ngn1: Callable[[int], int] = lambda x: x * 2\n\nassert pipe(v, fn1, gn1) == gn1(fn1(v))\n```\n\nExpression objects (e.g., `Some`, `Seq`, `Result`) also have a `pipe` method, so you can\ndot chain pipelines directly on the object:\n\n```python\nfrom expression import Option, Some\n\n\nv = Some(1)\nfn2: Callable[[Option[int]], Option[int]] = lambda x: x.map(lambda y: y + 1)\ngn2: Callable[[Option[int]], Option[int]] = lambda x: x.map(lambda y: y * 2)\n\nassert v.pipe(fn2, gn2) == gn2(fn2(v))\n```\n\nSo for example with sequences you may create sequence transforming\npipelines:\n\n```python\nfrom collections.abc import Callable\n\nfrom expression.collections import Seq, seq\n\n\n# Since static type checkes aren't good good at inferring lambda types\nmapper: Callable[[int], int] = lambda x: x * 10\npredicate: Callable[[int], bool] = lambda x: x \u003e 100\nfolder: Callable[[int, int], int] = lambda s, x: s + x\n\nxs = Seq.of(9, 10, 11)\nys = xs.pipe(\n    seq.map(mapper),\n    seq.filter(predicate),\n    seq.fold(folder, 0),\n)\n\nassert ys == 110\n```\n\n### Composition\n\nFunctions may even be composed directly into custom operators:\n\n```python\nfrom expression import compose\nfrom expression.collections import Seq, seq\n\n\nmapper: Callable[[int], int] = lambda x: x * 10\npredicate: Callable[[int], bool] = lambda x: x \u003e 100\nfolder: Callable[[int, int], int] = lambda s, x: s + x\n\nxs = Seq.of(9, 10, 11)\ncustom = compose(\n    seq.map(mapper),\n    seq.filter(predicate),\n    seq.fold(folder, 0),\n)\nys = custom(xs)\n\nassert ys == 110\n```\n\n### Fluent and Functional\n\nExpression can be used both with a fluent or functional syntax (or both.)\n\n#### Fluent syntax\n\nThe fluent syntax uses methods and is very compact. But it might get you into trouble\nfor large pipelines since it's not a natural way of adding line breaks.\n\n```python\nfrom expression.collections import Seq\n\n\nxs = Seq.of(1, 2, 3)\nys = xs.map(lambda x: x * 100).filter(lambda x: x \u003e 100).fold(lambda s, x: s + x, 0)\n```\n\nNote that fluent syntax is probably the better choice if you use mypy for type checking\nsince mypy may have problems inferring types through larger pipelines.\n\n#### Functional syntax\n\nThe functional syntax is a bit more verbose but you can easily add new operations on new\nlines. The functional syntax is great to use together with pylance/pyright.\n\n```python\nfrom expression import pipe\nfrom expression.collections import Seq, seq\n\n\nmapper: Callable[[int], int] = lambda x: x * 100\n\nxs = Seq.of(1, 2, 3)\nys = pipe(\n    xs,\n    seq.map(mapper),\n    seq.filter(lambda x: x \u003e 100),\n    seq.fold(lambda s, x: s + x, 0),\n)\n```\n\nBoth fluent and functional syntax may be mixed and even pipe can be used\nfluently.\n\n```python\nfrom expression.collections import Seq, seq\n\n\nxs = Seq.of(1, 2, 3).pipe(seq.map(mapper))\n```\n\n\n### Option\n\nThe `Option` type is used when a function or method cannot produce a meaningful\noutput for a given input.\n\nAn option value may have a value of a given type, i.e., `Some(value)`, or it might\nnot have any meaningful value, i.e., `Nothing`.\n\n```python\nfrom expression import Nothing, Option, Some\n\n\ndef keep_positive(a: int) -\u003e Option[int]:\n    if a \u003e 0:\n        return Some(a)\n\n    return Nothing\n```\n\n```python\nfrom typing import Literal\n\nfrom expression import Ok, Option\n\n\ndef exists(x: Option[int]) -\u003e bool:\n    match x:\n        case Option(tag=\"some\"):\n            return True\n        case _:\n            return False\n```\n\n### Option as an effect\n\nEffects in Expression is implemented as specially decorated coroutines\n([enhanced generators](https://www.python.org/dev/peps/pep-0342/)) using\n`yield`, `yield from` and `return` to consume or generate optional values:\n\n```python\nfrom collections.abc import Generator\n\nfrom expression import Some, effect\n\n\n@effect.option[int]()\ndef fn3() -\u003e Generator[int, int, int]:\n    x = yield 42\n    y = yield from Some(43)\n\n    return x + y\n\n\nxs = fn3()\n```\n\nThis enables [\"railway oriented programming\"](https://fsharpforfunandprofit.com/rop/),\ne.g., if one part of the function yields from `Nothing` then the function is\nside-tracked (short-circuit) and the following statements will never be executed. The\nend result of the expression will be `Nothing`. Thus results from such an option\ndecorated function can either be `Ok(value)` or `Error(error_value)`.\n\n```python\nfrom collections.abc import Generator\n\nfrom expression import Nothing, Some, effect\n\n\n@effect.option[int]()\ndef fn4() -\u003e Generator[int, int, int]:\n    x = yield from Nothing  # or a function returning Nothing\n\n    # -- The rest of the function will never be executed --\n    y = yield from Some(43)\n\n    return x + y\n\n\nxs = fn4()\nassert xs is Nothing\n```\n\n### Option as an applicative\n\nIn functional programming, we sometimes want to combine two Option values into a new\nOption. However, this combination should only happen if both Options are Some. If either\nOption is None, the resulting value should also be None.\n\nThe map2 function allows us to achieve this behavior. It takes two Option values and a\nfunction as arguments. The function is applied only if both Options are Some, and the\nresult becomes the new Some value. Otherwise, map2 returns None.\n\nThis approach ensures that our combined value reflects the presence or absence of data\nin the original Options.\n\n```python\nfrom operator import add\n\nfrom expression import Nothing, Option, Some\n\n\ndef keep_positive(a: int) -\u003e Option[int]:\n    if a \u003e 0:\n        return Some(a)\n    else:\n        return Nothing\n\n\ndef add_options(a: Option[int], b: Option[int]):\n    return a.map2(add, b)\n\n\nassert add_options(keep_positive(4), keep_positive(-2)) is Nothing\n\nassert add_options(keep_positive(3), keep_positive(2)) == Some(5)\n```\n\nFor more information about options:\n\n- [Tutorial](https://expression.readthedocs.io/en/latest/tutorial/optional_values.html)\n- [API reference](https://expression.readthedocs.io/en/latest/reference/option.html)\n\n\n### Result\n\nThe `Result[T, TError]` type lets you write error-tolerant code that can be composed. A\nResult works similar to `Option`, but lets you define the value used for errors, e.g.,\nan exception type or similar. This is great when you want to know why some operation\nfailed (not just `Nothing`). This type serves the same purpose of an `Either` type where\n`Left` is used for the error condition and `Right` for a success value.\n\n```python\nfrom expression import Ok, Result, effect\n\n\n@effect.result[int, Exception]()\ndef fn5() -\u003e Generator[int, int, int]:\n    x = yield from Ok(42)\n    y = yield from Ok(10)\n    return x + y\n\n\nxs = fn5()\nassert isinstance(xs, Result)\n```\n\nA simplified type called `Try` is also available. It's a result type that is\npinned to `Exception` i.e., `Result[TSource, Exception]`.\n\n\n### AsyncResult\n\nThe `AsyncResult[T, TError]` type is the asynchronous version of `Result`. It allows you\nto compose asynchronous operations that may fail, using the Result type. This is\nparticularly useful for handling errors in asynchronous code, such as API calls,\ndatabase operations, or any other I/O-bound tasks.\n\nSimilar to the `Result` effect, AsyncResult enables \"railway oriented programming\" but\nfor asynchronous operations. If any part of the function yields an `Error`, the function\nis short-circuited and the following statements will never be executed.\n\n```python\nfrom collections.abc import AsyncGenerator\n\nfrom expression import Error, Ok, effect\n\n\n@effect.async_result[int, str]()\nasync def fn() -\u003e AsyncGenerator[int, int]:\n    x: int = yield 42  # Regular value\n    y: int = yield await Ok(43)  # Awaitable Ok value\n\n    # Short-circuit if condition is met\n    if x + y \u003e 80:\n        z: int = yield await Error(\"Value too large\")  # This will short-circuit\n    else:\n        z: int = yield 44\n\n    yield x + y + z  # Final value\n\n\n# This would be run in an async context\n# result = await fn()\n# assert result == Error(\"Value too large\")\n```\n\nAsyncResult works well with other async functions and can be nested:\n\n\n```python\n@effect.async_result[int, str]()\nasync def inner(x: int) -\u003e AsyncGenerator[int, int]:\n    y: int = yield x + 1\n    yield y + 1  # Final value is y + 1\n\n\n@effect.async_result[int, str]()\nasync def outer() -\u003e AsyncGenerator[int, int]:\n    x: int = yield 40\n\n    # Call inner and await its result\n    inner_result = await inner(x)\n    y: int = yield await inner_result\n\n    yield y  # Final value is y\n\n\n# This would be run in an async context\n# result = await outer()\n# assert result == Ok(42)  # 40 -\u003e 41 -\u003e 42\n```\n\nA simplified type called `AsyncTry` is also available. It's an async result type that is\npinned to `Exception` i.e., `AsyncResult[TSource, Exception]`.\n\n\n### AsyncOption\n\nThe `AsyncOption[T]` type is the asynchronous version of `Option`. It allows you to\ncompose asynchronous operations that may return an optional value, using the Option type.\nThis is particularly useful for handling optional values in asynchronous code, such as\nAPI calls that might not return a value, database queries that might not find a record,\nor any other I/O-bound tasks that might not produce a meaningful result. The AsyncOption\nbuilder was added in version 0.25.0.\n\nSimilar to the `Option` effect, AsyncOption enables short-circuiting but for asynchronous\noperations. If any part of the function yields `Nothing`, the function is short-circuited\nand the following statements will never be executed.\n\n```python\nfrom collections.abc import AsyncGenerator\n\nfrom expression import Nothing, Some, effect\n\n\n@effect.async_option[int]()\nasync def fn_option() -\u003e AsyncGenerator[int, int]:\n    x: int = yield 42  # Regular value\n    y: int = yield await Some(43)  # Awaitable Some value\n\n    # Short-circuit if condition is met\n    if x + y \u003e 80:\n        z: int = yield await Nothing  # This will short-circuit\n    else:\n        z: int = yield 44\n\n    yield x + y + z  # Final value\n\n\n# This would be run in an async context\n# result = await fn_option()\n# assert result is Nothing\n```\n\nAsyncOption works well with other async functions and can be nested:\n\n\n```python\n@effect.async_option[int]()\nasync def inner_option(x: int) -\u003e AsyncGenerator[int, int]:\n    y: int = yield x + 1\n    yield y + 1  # Final value is y + 1\n\n\n@effect.async_option[int]()\nasync def outer_option() -\u003e AsyncGenerator[int, int]:\n    x: int = yield 40\n\n    # Call inner and await its result\n    inner_result = await inner_option(x)\n    y: int = yield await inner_result\n\n    yield y  # Final value is y\n\n\n# This would be run in an async context\n# result = await outer_option()\n# assert result == Some(42)  # 40 -\u003e 41 -\u003e 42\n```\n\n### Sequence\n\nSequences is a thin wrapper on top of iterables and contains operations for working with\nPython iterables. Iterables are immutable by design, and perfectly suited for functional\nprogramming.\n\n```python\nimport functools\nfrom collections.abc import Iterable\n\nfrom expression import pipe\nfrom expression.collections import seq\n\n\n# Normal python way. Nested functions are hard to read since you need to\n# start reading from the end of the expression.\nxs: Iterable[int]\nxs = range(100)\nys = functools.reduce(lambda s, x: s + x, filter(lambda x: x \u003e 100, map(lambda x: x * 10, xs)), 0)\n\nmapper: Callable[[int], int] = lambda x: x * 10\npredicate: Callable[[int], bool] = lambda x: x \u003e 100\nfolder: Callable[[int, int], int] = lambda s, x: s + x\n\n# With Expression, you pipe the result, so it flows from one operator to the next:\nzs: int = pipe(\n    xs,\n    seq.map(mapper),\n    seq.filter(predicate),\n    seq.fold(folder, 0),\n)\nassert ys == zs\n```\n\n## Tagged Unions\n\nTagged Unions (aka discriminated unions) may look similar to normal Python Unions. But\nthey are [different](https://stackoverflow.com/a/61646841) in that the operands in a\ntype union `(A | B)` are both types, while the cases in a tagged union type `U = A | B`\nare both constructors for the type U and are not types themselves. One consequence is\nthat tagged unions can be nested in a way union types might not.\n\nIn Expression you make a tagged union by defining your type similar to a `dataclass` and\ndecorate it with `@tagged_union` and add the appropriate generic types that this union\nrepresent for each case. Then you optionally define static or class-method constructors\nfor creating each of the tagged union cases.\n\n```python\nfrom dataclasses import dataclass\nfrom typing import Literal\n\nfrom expression import case, tag, tagged_union\n\n\n@dataclass\nclass Rectangle:\n    width: float\n    length: float\n\n\n@dataclass\nclass Circle:\n    radius: float\n\n\n@tagged_union\nclass Shape:\n    tag: Literal[\"rectangle\", \"circle\"] = tag()\n\n    rectangle: Rectangle = case()\n    circle: Circle = case()\n\n    @staticmethod\n    def Rectangle(width: float, length: float) -\u003e \"Shape\":\n        \"\"\"Optional static method for creating a tagged union case\"\"\"\n        return Shape(rectangle=Rectangle(width, length))\n\n    @staticmethod\n    def Circle(radius: float) -\u003e \"Shape\":\n        \"\"\"Optional static method for creating a tagged union case\"\"\"\n        return Shape(circle=Circle(radius))\n```\n\nNote that the tag field is optional, but recommended. If you don't specify a tag field\nthen then it will be created for you, but static type checkers will not be able to type\ncheck correctly when pattern matching. The `tag` field if specified should be a literal\ntype with all the possible values for the tag. This is used by static type checkers to\ncheck exhaustiveness of pattern matching.\n\nEach case is given the `case()` field initializer. This is optional, but recommended for\nstatic type checkers to work correctly. It's not required for the code to work properly,\n\nNow you may pattern match the shape to get back the actual value:\n\n```python\nshape = Shape.Rectangle(2.3, 3.3)\n\nmatch shape:\n    case Shape(tag=\"rectangle\", rectangle=Rectangle(width=2.3)):\n        assert shape.rectangle.width == 2.3\n    case _:\n        assert False\n```\n\nNote that when matching keyword arguments, then the `tag` keyword argument must be\nspecified for static type checkers to check exhaustiveness correctly. It's not required\nfor the code to work properly, but it's recommended to avoid typing errors.\n\n\u003c!-- #region --\u003e\n## Notable differences between Expression and F\\#\n\nIn F# modules are capitalized, in Python they are lowercase\n([PEP-8](https://www.python.org/dev/peps/pep-0008/#package-and-module-names)). E.g in F#\n`Option` is both a module (`OptionModule` internally) and a type. In Python the module\nis `option` and the type is capitalized i.e `Option`.\n\nThus in Expression you use `option` as the module to access module functions such as\n`option.map` and the name `Option` for the type itself.\n\n```python\n\u003e\u003e\u003e from expression import Option, option\n\u003e\u003e\u003e Option\n\u003cclass'expression.core.option.Option'\u003e\n\u003e\u003e\u003e option\n\u003cmodule 'expression.core.option' from '/Users/dbrattli/Developer/Github/Expression/expression/core/option.py'\u003e\n```\n\u003c!-- #endregion --\u003e\n\n## Common Gotchas and Pitfalls\n\nA list of common problems and how you may solve it:\n\n### Expression is missing the function/operator I need\n\nRemember that everything is just a function, so you can easily implement\na custom function yourself and use it with Expression. If you think the\nfunction is also usable for others, then please open a PR to include it\nwith Expression.\n\n\n## Resources and References\n\nA collection of resources that were used as reference and inspiration\nfor creating this library.\n\n- F# (\u003chttp://fsharp.org\u003e)\n- Get Started with F# (\u003chttps://aka.ms/fsharphome\u003e)\n- F# as a Better Python - Phillip Carter - NDC Oslo 2020\n  (\u003chttps://www.youtube.com/watch?v=_QnbV6CAWXc\u003e)\n- OSlash (\u003chttps://github.com/dbrattli/OSlash\u003e)\n- RxPY (\u003chttps://github.com/ReactiveX/RxPY\u003e)\n- PEP 8 -- Style Guide for Python Code (\u003chttps://www.python.org/dev/peps/pep-0008/\u003e)\n- PEP 342 -- Coroutines via Enhanced Generators\n  (\u003chttps://www.python.org/dev/peps/pep-0342/\u003e)\n- PEP 380 -- Syntax for Delegating to a Subgenerator\n  (\u003chttps://www.python.org/dev/peps/pep-0380\u003e)\n- PEP 479 -- Change StopIteration handling inside generators (\u003chttps://www.python.org/dev/peps/pep-0479/\u003e)\n- PEP 634 -- Structural Pattern Matching (\u003chttps://www.python.org/dev/peps/pep-0634/\u003e)\n- Thunks, Trampolines and Continuation Passing\n  (\u003chttps://jtauber.com/blog/2008/03/30/thunks,_trampolines_and_continuation_passing/\u003e)\n- Tail Recursion Elimination\n  (\u003chttp://neopythonic.blogspot.com/2009/04/tail-recursion-elimination.html\u003e)\n- Final Words on Tail Calls\n  (\u003chttp://neopythonic.blogspot.com/2009/04/final-words-on-tail-calls.html\u003e)\n- Python is the Haskell You Never Knew You Had: Tail Call Optimization\n  (\u003chttps://sagnibak.github.io/blog/python-is-haskell-tail-recursion/\u003e)\n\n\u003c!-- #region --\u003e\n## How-to Contribute\n\nYou are very welcome to contribute with suggestions or PRs :heart_eyes: It is\nnice if you can try to align the code and naming with F# modules, functions,\nand documentation if possible. But submit a PR even if you should feel unsure.\n\nCode, doc-strings, and comments should also follow the [Google Python Style\nGuide](https://google.github.io/styleguide/pyguide.html).\n\nCode checks are done using\n\n- [Ruff](https://github.com/astral-sh/ruff)\n\nTo run code checks on changed files every time you commit, install the pre-commit hooks\nby running:\n\n```bash\n\u003e pre-commit install\n```\n\u003c!-- #endregion --\u003e\n\n## Code of Conduct\n\nThis project follows \u003chttps://www.contributor-covenant.org\u003e, see our [Code\nof\nConduct](https://github.com/cognitedata/Expression/blob/main/CODE_OF_CONDUCT.md).\n\n## License\n\nMIT, see [LICENSE](https://github.com/cognitedata/Expression/blob/main/LICENSE).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdbrattli%2Fexpression","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdbrattli%2Fexpression","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdbrattli%2Fexpression/lists"}