Metadata-Version: 2.1
Name: sumtype
Version: 0.9.5.post1
Summary: A namedtuple-style library for defining immutable sum types in Python.
Home-page: UNKNOWN
Author: J Uryga
Author-email: lolzatu2@gmail.com
License: UNKNOWN
Project-URL: Source, https://github.com/lubieowoce/sumtype
Description: # `sumtype`
        A `namedtuple`-style library for defining immutable **sum types** in Python.
        
        > *You may know **sum types** under a different name –
        > they're also referred to as `tagged unions`, `enums` in Rust/Swift, and `variants` in C++. 
        > If you haven't heard about them yet, [here's](https://chadaustin.me/2015/07/sum-types/) a nice introduction.*
        
        The current version is `0.9.5`, quickly approaching `1.0`.
        The library supports Python 3.x
        (on versions <= 3.4, please install [`typing`](https://pypi.org/project/typing/)).
        The core code has lived in various `utils` folders for about a year,
        before I got tired of copying it around and decided to release it as an independent package.
        (see also: [Should I use it?](https://github.com/lubieowoce/sumtype#should-i-use-it))
        
        Suggestions, feedback and contributions are very welcome!
        
        
        
        ### A quick tour
        ```python
            >>> from sumtype import sumtype
            >>>
            >>> class Thing(sumtype):
            ...     def Foo(x: int, y: int): ...
            ...     def Bar(y: str, hmm: str): ...
            ...     def Zap(): ...
            ...
            >>>
        ```
        This means that a `Thing` value can be one of three variants:
        - a `Foo` with two `int` fields, `x` and `y`
        - a `Bar` with two `string` fields, `y` and `hmm`
        - a `Zap` with no fields
        
        You can also add your own docstring and methods in the class definition.
        If you prefer `namedtuple`-style definitions, `sumtype` supports those too - see `Thing2` in `sumtype.sumtype.demo()` for an example.
        
        #### Creating values and attribute access
        ```python
            >>> foo = Thing.Foo(x=3, y=5)          # named arguments
            >>> bar = Thing.Bar('hello', 'world')  # positional arguments
            >>> zap = Thing.Zap()
        ```
        Note that they're still just different values of the same type, not subclasses:
        ```python
            >>> type(foo) is Thing  and  type(bar) is Thing  and  type(zap) is Thing
            True
        ```
        
        Every specified field gets an accessor:
        ```python
            >>> foo.x, foo.y;
            (3, 5)
            >>> bar.y,  bar.hmm
            ('hello', 'world')
        ```
        ...with checks if the access is valid and descriptive error messages:
        ```python
            >>> Thing.Zap().hmm  # only `Bar`s have a `hmm` field
            Traceback (most recent call last):
              ...
            AttributeError: Incorrect 'Thing' variant: Field 'hmm' not declared in variant 'Zap'...
            >>>
            >>> Thing.Foo(x=1, y=2).blah_blah_blah  # no variant has a `blah_blah_blah` field 
            Traceback (most recent call last):
              ...
            AttributeError: Unknown attribute: Field 'blah_blah_blah' not declared in any variant of 'Thing'...
        ```
        
        The values also have a nice `__repr__()`:
        ```python
            >>> foo; bar; zap
            Thing.Foo(x=3, y=5)
            Thing.Bar(y='hello', hmm='world')
            Thing.Zap()
        ```
        
        The library is designed with efficiency in mind¹ – it uses `__slots__` for attribute storage
        and generates specialized versions of all the methods for each class.
        To see the generated code, do ` class Thing(sumtype, verbose=True):`.
        
        ¹ At least I like to think so ;)  I try to do my best with profiling things though!
        
        
        ### Features
        
        #### Equality and hashing
        ```python
            >>> Thing.Foo(1,2) == Thing.Foo(1,2)
            True
            >>> Thing.Foo(1,2) == Thing.Bar('a', 'b');
            False
            >>> {foo, foo, bar, zap} == {foo, bar, zap}
            True
        ```
        `__eq__` and `__hash__` pay attention to variant - even if we had a variant `Moo(x: int, y: int)`,
        `Foo(1,2) != Moo(1,2)` and `hash(Foo(1,2)) != hash(Moo(1,2))`.
        
        > **Note**: *Just like tuples, `sumtypes` `__eq__`/`__hash__` work by `__eq__`ing/`__hash__`ing the values inside,
        so the values must all implement the relevant method for it to work.*
        
        
        #### Modifying values
        ```python
            >>> foo;  foo.replace(x=99)
            Thing.Foo(x=3, y=5)
            Thing.Foo(x=99, y=5)
            >>>
            >>> bar;  bar.replace(y='abc', hmm='xyz')
            Thing.Bar(y='hello', hmm='world')
            Thing.Bar(y='abc', hmm='xyz')
        ```
        `foo.replace(x=99)` returns a new value, just like in `namedtuple`.
        
        > **Note**: *`replace` and all the other methods have underscored aliases (`_replace`).
        So even if you have a field called `replace`, you can still use `my_value._replace(x=15)`.*
        
        
        #### Pattern matching
        ##### Statement form:
        ```python
            >>> def do_something(val: Thing):
            ...     if val.is_Foo():
            ...         print(val.x * val.y)
            ...     elif val.is_Bar():
            ...         print('The result is', val.y, val.hmm)
            ...     elif val.is_Zap():
            ...         print('Whoosh!')
            ...     else: val.impossible() # throws an error - nice if you like having all cases covered
            ...
            >>> for val in (foo, bar, zap):
            ...     do_something(val)
            ...
            15
            The result is hello world
            Whoosh!
        ```
        ##### Expression form:
        ```python
            >>> [ val.match(
            ...      Foo = lambda x, y: x*y, 
            ...      Zap = lambda: 999,
            ...      _   = lambda: -1 # default case
            ...   )
            ...  for val in (foo, bar, zap)]
            [15, -1, 999]
        ```
        
        
        #### Conversions between `sumtypes` and standard types
        To...
        ```python
            >>> foo.values();  foo.values_dict();
            (3, 5)
            OrderedDict([('x', 3), ('y', 5)])
        ```
        ```python
            >>> foo.as_tuple();  foo.as_dict()
            ('Foo', 3, 5)
            OrderedDict([('variant', 'Foo'), ('x', 3), ('y', 5)])
        ```
        ...and from
        ```python
            >>> Thing.from_tuple(('Foo', 10, 15));  Thing.from_dict({'variant':'Foo', 'x': 10, 'y': 15})
            Thing.Foo(x=10, y=15)
            Thing.Foo(x=10, y=15)
        ```
        Also, `x == Thing.from_tuple(x.as_tuple())` and `x == Thing.from_dict(x.as_dict())`.
        
        
        #### Pickle support
        ```python
            >>> import pickle
            >>> vals  = [Thing.Foo(1, 2), Thing.Bar('one', 'two'), Thing.Zap()]
            >>> vals2 = pickle.loads(pickle.dumps(vals))
            >>> vals;  vals == vals2
            [Thing.Foo(x=1, y=2), Thing.Bar(y='one', hmm='two'), Thing.Zap()]
            True
        ```
        
        There's also tests in `sumtype.tests` to ensure that it all works correctly.
        And that's everything... for now!
        
        
        ### Features coming in `1.0`
        - Default values
        *(easy, just haven't gotten around to it yet)*
        
        - Argument typechecking – always, or in `__debug__` mode only
        *(less easy because of `typing` annotations like `Tuple[int, str]` –
        `typing` does not expose a way to check if a value matches a type like that)*
        
        
        ### Should I use it?
        Yeah! I didn't just build this library because I thought it'd be nice –
        I'm using it heavily in an app I'm developing and a few smaller projects.
        Saying that it's battle-tested is a bit much, but it's getting there.
        
        
        ### Possible future features
        
        - `mypy` support.
        Unfortunately, last time I checked, `mypy` didn't handle metaclass-created classes too well, but that might have changed.
        Alternatively, we could provide a way to generate `mypy` stub files. Also, right now there's no way to tell `mypy` that
        the return type of accessors depend on the variant – `Union[a, b]` is close, but `mypy` will complain that not all cases
        are handled even if they are.
        
        - Statically generating a class definition to a file
        
        - Dynamic alternatives to custom-generated methods –
        might be useful if startup time is more important than efficiency
        
        - An alternative implementation backed by tuples if true immutability is desired –
        there's currently no way to make a `__slots__`-based implementation watertight in that aspect, I'm doing my best.
        
        - **Maybe** opt-in mutability – currently, you can use `Thing._unsafe_set_Foo_x(foo, 10)` if you want that, but that's not a nice interface
        
Keywords: sum variant tagged union enum type types
Platform: UNKNOWN
Description-Content-Type: text/markdown
