Skip to content

Commit

Permalink
Chore/prepare 0.13.0 (#97)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sune Debel authored Oct 24, 2021
1 parent 46c4606 commit 04f98f6
Show file tree
Hide file tree
Showing 17 changed files with 85 additions and 51 deletions.
8 changes: 4 additions & 4 deletions docs/effectful_but_side_effect_free.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ response.run(...) # What could this argument be?
```
To call the `response.run` function, we need an instance of a type that is a `str` and a `Credentials` instance _at the same time_, because that argument must be passed to both the effect returned by `execute` and by `make_request`. Ideally, we want `response` to have the type `Effect[Intersection[Credentials, str], IOError, bytes]`, where `Intersection[Credentials, str]` indicates that the dependency type must be both of type `Credentials` and of type `str`.

In theory such an object could exist (defined as `class MyEnv(Credentials, str): ...`), but there are no straight-forward way of expressing that type dynamically in the Python type system. As a consequence, `pfun` infers the resulting effect with the `R` parameterized as `typing.Any`, which in this case means that `pfun` could not assign a meaningful type to `R`.
In theory such an object could exist (defined as `class MyEnv(Credentials, str): ...`), but there is no straight-forward way of expressing that type dynamically in the Python type system. As a consequence, `pfun` infers the resulting effect with the `R` parameterized as `typing.Any`, which in this case means that `pfun` could not assign a meaningful type to `R`.

If you use the `pfun` MyPy plugin, you can however redesign the program to follow a pattern that enables `pfun` to infer a meaningful combined type
in much the same way that the error type resulting from combining two effects using `and_then` can be inferred. This pattern is called _the module pattern_.
Expand All @@ -250,9 +250,9 @@ In many cases the api for effects involved in the module pattern is split into t

- A _module_ class that provides the actual implementation
- A _module provider_ that is a `typing.Protocol` that provides the module class as an attribute
- Functions that return effects with the module provider class as the dependency type.
- Functions that return effects with the module provider class as the dependency type. (Although these functions are only for convenience and can be omitted)

Lets rewrite our example from before to follow the module pattern:
Let's rewrite our example from before to follow the module pattern:
```python
from typing import Protocol
from http.client import HTTPError
Expand Down Expand Up @@ -558,7 +558,7 @@ assert slow_effect.race(fast_effect).run(DefaultModules()) == 'Born ready!'
simply a type-alias:

```python
from typing import Iterator
from typing import TypeVar, Iterator
import datetime

from pfun.effect import Depends
Expand Down
Empty file.
File renamed without changes.
3 changes: 2 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@ theme:
primary: blue
logo: nav_logo.svg
favicon: nav_logo.svg
custom_dir: docs/overrides
markdown_extensions:
- codehilite
- admonition
plugins:
- search
- mkdocstrings:
custom_templates: templates
custom_templates: docs/templates
handlers:
python:
rendering:
Expand Down
8 changes: 4 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pfun"
version = "0.12.3"
version = "0.13.0"
description = "Functional, composable, asynchronous, type-safe Python."
authors = ["Sune Debel"]
readme = "README.md"
Expand Down
4 changes: 2 additions & 2 deletions src/pfun/dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ def __init__(self, d: Mapping[K, V] = dict()):
d = d._d
object.__setattr__(self, '_d', dict(d))

def __repr__(self):
def __repr__(self) -> str:
return f'Dict({repr(self._d)})'

def __eq__(self, other) -> bool:
def __eq__(self, other: object) -> bool:
"""
Compare `self` with `other`
Expand Down
19 changes: 10 additions & 9 deletions src/pfun/either.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

from abc import ABC, abstractmethod
from functools import wraps
from typing import Any, Callable, Generic, Iterable, TypeVar, Union, cast
from typing import (Any, Callable, Generic, Iterable, NoReturn, TypeVar, Union,
cast)

from typing_extensions import Literal

Expand All @@ -24,7 +25,7 @@ class Either_(Immutable, Monad, ABC):
use `Left` or `Right` instead
"""
@abstractmethod
def and_then(self, f):
def and_then(self, f: Callable[[Any], 'Either']) -> 'Either':
"""
Chain together functions of either computations, keeping
track of whether or not any of them have failed
Expand Down Expand Up @@ -63,7 +64,7 @@ def __bool__(self) -> bool:
raise NotImplementedError()

@abstractmethod
def or_else(self, default):
def or_else(self, default: Any) -> Any:
"""
Try to get the result of this either computation, return default
if this is a ``Left`` value
Expand All @@ -83,7 +84,7 @@ def or_else(self, default):
raise NotImplementedError()

@abstractmethod
def map(self, f):
def map(self, f: Callable[[Any], Any]) -> 'Either':
"""
Map the result of this either computation
Expand Down Expand Up @@ -145,7 +146,7 @@ def __eq__(self, other: Any) -> bool:
def __bool__(self) -> Literal[True]:
return True

def __repr__(self):
def __repr__(self) -> str:
return f'Right({repr(self.get)})'


Expand Down Expand Up @@ -188,7 +189,7 @@ def __bool__(self) -> Literal[False]:
def and_then(self, f: Callable[[A], Either[B, C]]) -> Either[B, C]:
return self

def __repr__(self):
def __repr__(self) -> str:
return f'Left({repr(self.get)})'


Expand All @@ -199,7 +200,7 @@ def __repr__(self):
Either.__module__ = __name__


def either(f: Callable[..., A]) -> Callable[..., Either[A, B]]:
def either(f: Callable[..., A]) -> Callable[..., Either[NoReturn, A]]:
"""
Turn ``f`` into a monadic function in the ``Either`` monad by wrapping
in it a `Right`
Expand All @@ -214,7 +215,7 @@ def either(f: Callable[..., A]) -> Callable[..., Either[A, B]]:
``f`` wrapped with a ``Right``
"""
@wraps(f)
def decorator(*args, **kwargs):
def decorator(*args: object, **kwargs: object) -> Either[NoReturn, A]:
return Right(f(*args, **kwargs))

return decorator
Expand Down Expand Up @@ -335,7 +336,7 @@ def catch(f: Callable[..., A]) -> Callable[..., Either[Exception, A]]:
decorated function
"""
@wraps(f)
def decorator(*args, **kwargs) -> Either[Exception, A]:
def decorator(*args: object, **kwargs: object) -> Either[Exception, A]:
try:
return Right(f(*args, **kwargs))
except Exception as e:
Expand Down
16 changes: 8 additions & 8 deletions src/pfun/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class Always(Generic[A], Immutable):
"""
value: A

def __call__(self, *args, **kwargs) -> A:
def __call__(self, *args: object, **kwargs: object) -> A:
return self.value


Expand Down Expand Up @@ -78,7 +78,7 @@ def __repr__(self) -> str:
functions_repr = ', '.join(repr(f) for f in self.functions)
return f'compose({functions_repr})'

def __call__(self, *args, **kwargs):
def __call__(self, *args: object, **kwargs: object) -> Any:
fs = reversed(self.functions)
first, *rest = fs
last_result = first(*args, **kwargs)
Expand Down Expand Up @@ -123,7 +123,7 @@ def pipeline(
first: Callable[[Any], Any],
second: Callable[[Any], Any],
*rest: Callable[[Any], Any]
):
) -> Callable[[Any], Any]:
"""
Compose functions from right to left
Expand All @@ -135,8 +135,8 @@ def pipeline(
Args:
first: the innermost function in the composition
g: the function to compose with f
functions: functions to compose with `first` and \
second: The second-innermost function in the composition
rest: functions to compose with `first` and \
`second` from right to left
Return:
Expand All @@ -153,10 +153,10 @@ def __init__(self, f: Callable):
functools.wraps(f)(self)
self._f = f # type: ignore

def __repr__(self):
def __repr__(self) -> str:
return f'curry({repr(self._f)})'

def __call__(self, *args, **kwargs):
def __call__(self, *args: object, **kwargs: object) -> Any:
signature = inspect.signature(self._f)
bound = signature.bind_partial(*args, **kwargs)
bound.apply_defaults()
Expand Down Expand Up @@ -194,7 +194,7 @@ def curry(f: Callable) -> Callable:
Curried version of ``f``
"""
@functools.wraps(f)
def decorator(*args, **kwargs):
def decorator(*args: object, **kwargs: object) -> Any:
return Curry(f)(*args, **kwargs)

return decorator
Expand Down
11 changes: 7 additions & 4 deletions src/pfun/immutable.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ class Immutable:
"""

def __init_subclass__(
cls, init=True, repr=True, eq=True, order=False, unsafe_hash=False
):
def __init_subclass__(cls,
init: bool = True,
repr: bool = True,
eq: bool = True,
order: bool = False,
unsafe_hash: bool = False) -> None:
super().__init_subclass__()
if not hasattr(cls, '__annotations__'):
cls.__annotations__ = {}
return dataclass(
dataclass(
frozen=True, init=init, repr=repr, eq=eq, order=order
)(cls)

Expand Down
2 changes: 1 addition & 1 deletion src/pfun/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

class List(Monad, Tuple[A, ...]):

def __repr__(self):
def __repr__(self) -> str:
return f"List({super().__repr__()})"

def empty(self) -> 'List[A]':
Expand Down
4 changes: 2 additions & 2 deletions src/pfun/maybe.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def __eq__(self, other: Any) -> bool:
return False
return other.get == self.get

def __repr__(self):
def __repr__(self) -> str:
return f'Just({repr(self.get)})'

def __bool__(self) -> Literal[True]:
Expand Down Expand Up @@ -121,7 +121,7 @@ def maybe(f: Callable[..., B]) -> Callable[..., Maybe[B]]:
"""
@wraps(f)
def dec(*args, **kwargs):
def dec(*args: object, **kwargs: object) -> Maybe[B]:
try:
return Just(f(*args, **kwargs))
except: # noqa
Expand Down
4 changes: 2 additions & 2 deletions src/pfun/monad.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from abc import ABC, abstractmethod
from functools import reduce
from typing import Any, Callable, Iterable
from typing import Any, Callable, Iterable, Tuple

from .functions import curry
from .functor import Functor
Expand Down Expand Up @@ -50,7 +50,7 @@ def filter_m_(
f: Callable[[Any], Monad],
iterable: Iterable
) -> Monad:
def combine(ms, mbx):
def combine(ms: Monad, mbx: Tuple) -> Monad:
mb, x = mbx
return ms.and_then(
lambda xs: mb.and_then(lambda b: value(xs + (x, ) if b else xs))
Expand Down
4 changes: 2 additions & 2 deletions src/pfun/mypy_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def visit_type_alias_type(self, t: TypeAliasType) -> Type:
return t.accept(self)

def visit_instance(self, t: Instance) -> Type:
if 'pfun.Intersection' in t.type.fullname:
if 'pfun.Intersection' == t.type.fullname:
args = [get_proper_type(arg) for arg in t.args]
if any(isinstance(arg, AnyType) for arg in args):
return AnyType(TypeOfAny.special_form)
Expand Down Expand Up @@ -116,7 +116,7 @@ def visit_instance(self, t: Instance) -> Type:
bases.extend(self.get_bases(arg, []))
if len(bases) == 1:
return bases[0]
bases_repr = ', '.join(sorted([repr(base) for base in bases]))
bases_repr = ', '.join([repr(base) for base in bases])
name = f'Intersection[{bases_repr}]'
defn = ClassDef(
name,
Expand Down
2 changes: 1 addition & 1 deletion src/pfun/random.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class HasRandom(Protocol):
random: Random


def randint(a, b) -> Depends[HasRandom, int]:
def randint(a: int, b: int) -> Depends[HasRandom, int]:
"""
Create an `Effect` that succeeds with a random integer `n` in the range \
`a <= n <= b`.
Expand Down
3 changes: 2 additions & 1 deletion tests/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import random

import pytest
from hypothesis import assume, given
from hypothesis import assume, given, settings
from hypothesis.strategies import integers
from hypothesis.strategies import lists as lists_

Expand Down Expand Up @@ -39,6 +39,7 @@ def test_right_append_identity_law(self, l):
def test_append_associativity_law(self, x, y, z):
assert (x + y) + z == x + (y + z)

@settings(deadline=None)
@given(
lists(anything()),
unaries(lists(anything())),
Expand Down
Loading

0 comments on commit 04f98f6

Please sign in to comment.