This is an experiment in building monads in Python supported by strict type annotations. The goal is to be able to compose monads with the type checker ensuring their correctness.
I'm a fan of monads, but believe they work best with the support of a strong type system. I've attempted to use libraries like PyMonad, but been frustrated by a lack of type constraints preventing incorrect usage. I could've attempted to add type annotations to one of those libraries, but building my own is more fun.
*
)Applies a function to the contents of a functor, transforming it from one thing to another.
The *
operator implements map on functors, and is both left and
right associative:
def wordcount(s: str):
return len(s.split())
f.map(wordcount) == wordcount * f == f * wordcount
Extends Functor
.
Wraps a value in an applicative functor.
e.g.:
Maybe.pure("abc") == Just("abc")
Result.pure(123) == Ok(123)
&
)Transforms the value contained in the instance's functor with a function wrapped in the same type of functor.
The &
operator implements apply on applicatives, and is
right-associative.
e.g.:
increment = lambda x: x + 1
Just(3).apply(Just(increment)) == Just(increment) & Just(3) == Just(4)
This can be very handily combined with map to apply curried functions to multiple arguments:
subtract = lambda x: lambda y: x - y
subtract * Just(10) & Just(4) == Just(6)
Extends Applicative
.
>>
)Passes the value within the monad through an operation returning the same type of monad, allowing multiple operations to be chained.
The >>
operator implements bind on monads, and is left-associative.
@curry
def lookup(key: str, dictionary: Dict[str, str]) -> Maybe[str]:
try:
return Just(dictionary[key])
except KeyError:
return Nothing()
result = Just({"hello": "world"}).bind(lookup("hello")).bind(lambda s: s.upper())
result = (
Just({"hello": "world"})
>> lookup("hello")
>> (lambda s: s.upper())
)
+
)Describes an associative binary operation for a type.
Provides an identity value for the mappend
operation.
Accumulates a list of values using mappend
. Returns the mzero
value if the list is empty.
Represents optional data. A Maybe
instance of a certain type T
will
either be a Just
object wrapping a value of that type, or Nothing
.
Nothing
will return Nothing
without
calling the function.Nothing
will return Nothing
without
attempting the operation.Represents a state of success or failure, declaring a type for each. A
Result
instance will either be an Ok
object wrapping a value of
the success type T
, or an Err
object wrapping a value of the
failure type E
.
Err
will return the Err
unchanged
without calling the function.Err
will return the Err
unchanged
without attempting the operation.Represents a sequence of items.
Monoid
.Represents an asynchronous action.
Awaitable
.Represents the application of a function to it's argument.