- haskpy.types.maybe.Maybe
Maybe¶
- class Maybe(match)[source]¶
Bases:
Monad
,Commutative
,Monoid
,Hashable
,Traversable
Type
Maybe a
for a value that might be present or notMaybe
monad is one of the simplest yet very useful monads. It represents a case when you might have a value or not. If you have a value, it’s wrapped withJust()
:>>> x = hp.Just(42) >>> x Just(42) >>> type(x) Maybe
If you don’t have a value, it’s represented with
Nothing
:>>> y = hp.Nothing >>> y Nothing >>> type(y) Maybe
Quite often Python programmers handle this case by using
None
to represent “no value” and then just the plain value otherwise. However, whenever you want to do something with the value, you need to first check if it’sNone
or not, and handle both cases somehow. And, more importantly, you need to remember to do this every time you use a value that might beNone
.With HaskPy, it is explicit that the value might exist or not, so you are forced to handle both cases. Or, more interestingly, you can just focus on the value and let HaskPy take care of the special case. Let’s see what this means. Say you have a function that you’d like to apply to the value:
>>> f = lambda v: v + 1
You can use
Functor.map()
method to apply it to the value:>>> x.map(f) Just(43)
Or, equivalently, use a function:
>>> hp.map(f, x) Just(43)
Quite often there are corresponding functions for the methods and it may depend on the context which one is more convenient to use. The order of the arguments might be slightly different in the function than in the method though.
But what would’ve happened if we had
Nothing
instead?>>> hp.map(f, y) Nothing
So, nothing was done to
Nothing
. But the important thing is that you didn’t need to worry about whetherx
ory
wasNothing
or contained a real value,Maybe
took care of that under the hood. If in some cases you need to handle both cases explicitly, you can usematch()
function:>>> g = hp.match(Just=lambda v: 2*v, Nothing=lambda: 666) >>> g(x) 84 >>> g(y) 666
With
match()
you need to explicitly handle all possible cases or you will get an error even if your variable wasn’tNothing
. Therefore, you’ll never forget to take into accountNothing
case as might happen with the classicNone
approach.Alright, this was just a very tiny starter about
Maybe
.See also
- __add__(other)¶
Append two monoids
Using
+
operator to append two monoid values seems natural because that’s what Python is doing by default because lists are concatenated with+
.
- __annotations__ = {}¶
- __contains__(x)¶
Override elem if you want to change the default implementation
- __eq__(other)[source]¶
Mark method non-existing
This is a workaround for Python forcefully creating some methods. One cannot create objects that don’t have
__eq__
,__ge__
,__gt__
and many other methods. They are there and it’s not possible to delete them. With this wrapper you can override those methods so that they won’t show up in__dir__
listing and if accessed in any way,AttributeError
is raised. Note that it just hides the methods, one can still access them asobject.__getattribute__(obj, "__eq__")
.
- __hash__()[source]¶
Mark method non-existing
This is a workaround for Python forcefully creating some methods. One cannot create objects that don’t have
__eq__
,__ge__
,__gt__
and many other methods. They are there and it’s not possible to delete them. With this wrapper you can override those methods so that they won’t show up in__dir__
listing and if accessed in any way,AttributeError
is raised. Note that it just hides the methods, one can still access them asobject.__getattribute__(obj, "__eq__")
.
- __iter__()¶
Override to_iter if you want to change the default implementation
- __len__()¶
Override length if you want to change the default implementation
- __lshift__(x)¶
Sequence with
<<
similarly as with<*
and<<
in Haskell
- __matmul__(x)¶
Application operand
@
applies similarly as<*>
in Haskellf @ x
translates tof.apply_to(x)
,x.apply(f)
andapply(f, x)
.Why
@
operator?It’s not typically used as often as some other more common operators so less risk for confusion.
The operator is not a commutative as isn’t
apply
either.If we see matrix as some structure, then matrix multiplication takes both left and right operand inside this structure and gives a result also inside this structure, similarly as
apply
does. So it’s an operator for two operands having a similar structure.The operator evaluates the contained function(s) at the contained value(s). Thus,
f
“at”x
makes perfect sense.
- __mod__(f)¶
Use
%
as bind operator similarly as>>=
in HaskellThat is,
x % f
is equivalent tobind(x, f)
andx.bind(f)
.Why
%
operator?It’s not very often used so less risk for confusion.
It’s not commutative as isn’t bind either.
It is similar to bind in a sense that the result has the same unit as the left operand while the right operand has different unit.
The symbol works visually as a line “binds” two circles and on the other hand two circles tell about two similar structures on both sides but those structures are just on different “level”.
- __ne__(other)¶
Inequality comparison:
Eq a => a -> a -> bool
Can be used as
!=
operator.The default implementation uses
__eq__
.
- __rpow__(f)¶
Lifting operator
**
lifts similarly as<$>
in Haskellf ** x
translates tox.map(f)
andmap(f, x)
.Why
**
operator?It’s not typically used as often as multiplication or addition so less risk of confusion.
It’s not commutative operator as isn’t lifting either.
The two operands have very different roles. They are not at the same “level”.
The right operand is “higher”, that is, it’s inside a structure and the left operand is kind of “raised to the power” of the second operand, where the “power” is the functorial structure.
The same operand is also used for function composition because function composition is just mapping. Visually the symbol can be seen as chaining two stars similarly as function composition chains two functions.
- __rshift__(x)¶
Sequence with
>>
similarly as with*>
and>>
in Haskell
- apply(f)¶
m a -> m (a -> b) -> m b
self :: m a
f :: m (a -> b)
Default implementation is based on
bind
andmap
. In order to usebind
, let’s write its type as follows:bind :: m (a -> b) -> ((a -> b) -> m b) -> m b
Let’s also use a simple helper function:
h = g -> map g self :: (a -> b) -> m b
Now:
bind f h :: m b
- apply_first(x)¶
Combine two actions, keeping only the result of the first
Apply f => f a -> f b -> f a
- apply_second(x)¶
Combine two actions, keeping only the result of the second
Apply f => f a -> f b -> f b
- bind(f)[source]¶
m a -> (a -> m b) -> m b
Default implementation is based on
join
andmap
:self :: m a
f :: a -> m b
map f :: m a -> m (m b)
join :: m (m b) -> m b
- elem(x)¶
t a -> a -> bool
- flap(x)¶
Functor f => f (a -> b) -> a - > f b
- fold(monoid)¶
- fold2(monoid)¶
- fold_map(monoid, f)[source]¶
Monoid m => t a -> (a -> m) -> m (ignoring
monoid
argument)The default implementation is based on
foldl
(or, if not implemented, recursively onfoldr
). Thus, all possibilities for parallelism is lost.monoid
is the monoidic class of the values inside the foldable. It is only used to determine the identity value.
- foldl(combine, initial)[source]¶
t a -> (b -> a -> b) -> b -> b
The default implementation is based on
foldr
(or, if not implemented, recursively onfold_map
). Either way, the default implementation doesn’t scale up well, so an instance implementation is strongly recommended.Intuition:
equivalent to for-loop on lists
never works on infinite lists (so not possible to implement
map
in terms offoldl
)
References
- foldr(combine, initial)[source]¶
t a -> (a -> b -> b) -> b -> b
Warning
The default is very poor in Python. It is strongly recommended to provide an instance implementation for this.
The default implementation uses
fold_map
by utilizing (endo)function monoid:empty :: b -> b
append :: (b -> b) -> (b -> b) -> (b -> b)
One can see
combine
function as a transformation to this monoid:combine :: a -> (b -> b)
Then, just use endofunction monoid to compose all those
b -> b
endofunctions into a single endofunctionb -> b
. Finally, apply this function to the initial value.Intuition:
performs constructor replacement
may work on infinite lists
doesn’t “calculate from the right” but associates to the right (otherwise it couldn’t work on infinite lists)
References
- head(default)¶
Return head (or default if no head):
f a -> a -> a
- join()¶
m (m a) -> m a
Default implementation is based on
bind
:self :: m (m a)
identity :: m a -> m a
bind :: m (m a) -> (m a -> m a) -> m a
- length()[source]¶
t a -> int
The default implementation isn’t very efficient as it traverses through the iterator.
- map(f)[source]¶
m a -> (a -> b) -> m b
Default implementation is based on
bind
andpure
. This implementation needs to be provided because the default implementation ofapply
usesmap
thus creating a circular dependency between the defaultmap
defined inApplicative
.
- null()¶
t a -> bool
- replace(x)¶
Haskell ($>) operator
- sequence(applicative)[source]¶
Evalute each action in the structure and collect the results
For
Traversable t
:Applicative f => t (f a) -> f (t a)
The default implementation is based on
traverse
.
- sum()¶
t a -> number
- to_iter()[source]¶
t a -> Iter a
Instead of to_list (as in Haskell), let’s provide to_iter. With iterables, we can write efficient implementations for many other methods (e.g., sum, elem) even for large or sometimes infinite foldables.
The default implementation isn’t very efficient as it uses folding to construct the iterator.
- traverse(applicative, func)¶
Map each element to an action and collect the results
For
Traversable t
:Applicative f => t a -> (a -> f b) -> f (t b)
The default implementation is based on
sequence
.