from pynads import Writer from pynads.funcs import multibind from itertools import repeat from functools import partial pairs = ((5, 'fizz'), (3, 'buzz')) def fizzer(n, pairs): fizzed = ''.join([buzz for fizz, buzz in pairs if not n % fizz]) or n return Writer(n+1, {n:fizzed}) res = multibind(Writer(1, {}), *repeat(partial(fizzer, pairs=pairs), 15)) print(res)

Writer(16, {1: 1, 2: 2, 3: 'buzz', 4: 4, 5: 'fizz', 6: 'buzz', 7: 7, 8: 8, 9: 'buzz', 10: 'fizz', 11: 11, 12: 'buzz', 13: 13, 14: 14, 15: 'fizzbuzz'})

Truly an 11x Developer solution to the [STRIKEOUT:world's] universe's premier code interview question!

## What is "pynads"

All joking aside, I've been hacking together a collection of Haskell-esque tools for Python over the last few weeks. It started as a "Well, I'll learn Haskell better this way." and has become...well, honestly a tool for me learning Haskell still.

Check it out, I think it's nifty.

## A Quick Tour of Pynads

pynads strives to be a pythonic form of Haskell. Which is buzzword for I did some stuff. There's:

- Functors
- Applicatives
- Monads
- Monoids
- Helpers

All of the base classes are implemented as Abstract Base Classes which
makes inheritance easy. Well, that's a lie, the root object of pynads is
a concrete class that just serves as an endpoint for `__new__` and
`__init__`.

### Container

`pynads.Container` is the root object of every `pynads` class. It
serves as a final endpoint for `__new__` and `__init__` as well as
providing a consistent name for accessing the values held by objects in
`pynads`. Some would say that it's a silly idea, but it works! Every
class in `pynads` is also slotted for memory reasons since it's built
around the idea of not manipulating a container but creating a new one.

The only important thing to know about Container is that it defines
`v` as a property which actually delagates to the `_get_val` method.
Meaning that's all that needs to be overriden to get multiple values out
of a container.

For most subclasses of Container, the provided `__init__` is fine, but
it's a-ok to override it as well as the only setup that happens is
setting a single attribute `_v`.

from pynads import Container class Person(Container): __slots__ = ('name', 'age') def __init__(self, name, age): self.name = name self.age = age def _get_val(self): return {'name': self.name, 'age': self.age} def __repr__(self): return "Person(name={!s}, age={!s})".format(self.name, self.age) print(Person(name="Alec", age=26).v)

{'age': 26, 'name': 'Alec'}

### Functors

Functors are data types that can be mapped over with the `fmap`
method. But calling methods isn't very Haskell like, so there's an
operator that does the same thing: `%`.

from pynads import Functor class FmapPerson(Person, Functor): __slots__ = () def fmap(self, f): return self.__class__(f(self.name), self.age) print(str.upper % FmapPerson(name="Alec", age=26))

Person(name=ALEC, age=26)

Of course, you *could* just use `FmapPerson.fmap` but that's not very
Haskellic. `pynads` also exports a `funcs` name space that contains
functional shortcuts to some of these (though `f % functor` doesn't
get much shorter). In this case, there's also:

from pynads import funcs print(funcs.fmap(str.upper, FmapPerson(name="Alec", age=26)))

Person(name=ALEC, age=26)

Every class in the `pynads.concrete` namespace is a functor except for
`Mempty` (we'll get there!).

### Applicatives

Applicative Functors are functors that hold functions and can then be
applied to other Applicatives/Functors. Every class (except for
`Mempty`) in `pynads.concrete` is also an Applicative!

from pynads import Just print(Just(lambda x: x+2) * Just(2))

Just 4

Yeah, like Haskell there's an infix operator. Except of `<*>` I just
dropped the angle brackets because Python doesn't let you define custom
operators (easily). It also combines nicely with `%` because they have
the same precedence level!

print((lambda x: lambda y: x+y) % Just(4) * Just(6))

Just 10

BOOM! Mind blown! Whaaaaaat. Applicative uses the abstract method
`apply` to determine how `*` operates. Just inherit from
Applicative, define `fmap` and `apply` and boom, you're on your way.
Well, you also need the `unit` method -- which is a class method for
all `pynad` types, but that's not a requirement -- which knows how to
put a value in a minimal context.

But wait, what if you have a curried function and you stuffed it into a
Just and now you don't want to write out
`just_f * just_v1 * just_v2 ...`. Sure, you could think "Well, what if
I used `reduce(operator.mul, *justs)`" But I thought of that already.

add_three_together = lambda x: lambda y: lambda z: x+y+z print(funcs.multiapply(Just(add_three_together), *[Just(x) for x in range(1,4)]))

Just 6

If you're mind isn't blown yet, it's because I haven't revealed...

### MOOOOOOOOONAAAAAAAADS!!!!!

Monads get a bad rap because they get all sorts of overblown explainations. You want to know a what a monad is? It's another way to compute things. It's a glorified container with a special method. You have a value in a monad, a function that takes a regular value and returns a monad and you bind them together. That's it. Literally all there is to it.

from pynads import Nothing inc_if_odd_else_nothing = lambda x: Just(x+1) if not x&1 else Nothing print(Just(2) >> inc_if_odd_else_nothing)

Just 3

The Maybe monad (which consists of the Just and Nothing data types) is basically a glorified if expression. That's it! The bind operation will detect if you have a failure in your computation and short circuit it. It's essentially an abstraction over this:

def safe_func(x): if x is None: return None else: return x+1 print(safe_func(1), safe_func(None))

2 None

Notice how that didn't cause a nasty AttributeError, because None
doesn't have attributes? That's all Maybe lets you do (this behavior is
mimicked in its fmap and apply: `fmap(f, Nothing)` and
`apply(ap_f, Nothing)` both return you a Nothing). Nothing is
extraspecialsauce because it's a singleton. It's basically a monadic
None. Actually, it *is* a monadic None because it represents...well,
Nothing.

If you've got more binds than editor columns, then there's something for you as well!

print(multibind(Just(1), *repeat(lambda x: Just(x+1), 5)))

Just 6

Before you start thinking, "Well, monads are just glorified if
expressions" becuase that's missing the point, Monads are the
abstraction abstraction. They represent a way to compute something by
abstracting away *how* it's computated.

There's the Writer monad above which is a value in a monadic context but
it also keeps some extra side-output as well. Instead of us trying to
track this extra information ourselves, Writer says, "Hey, I'll handle
it for you!" It just wants a function that accepts a value and returns a
Writer. But here's the really cool thing, I *didn't* have to use a
dictionary. It could have a list, or a string or an integer, or a custom
class! I hear, "But how!" Through the power of...

### Monoids!

So monoids are pretty cool. They're a set of something, a "zero" and a
binary operator that's transative. Lots of things form monoids. Numbers
are monoids! There's *two* ways to make numbers monoids: with `0` and
`+`, with `1` and `*`. However, `pynads` is lazy and only
defines the first...sorry, hard choices were made.

Wait, "a zero value" but `1 != 0`. That's missing the point, a zero
value is a value that doesn't change the input when combined with the
binary operator. `x * 1 == x`.

But Python's "primitive" types all form monads!

`list`is a monoid. That's the zero value right there and it's binary operator would be`list.extend`if it was actually a binop.`dict`is a monoid with`{}`and`dict.update``bool`is a monoid with`False`and`|`\`or``set`and`frozenset`are also monoids with their empty instances and`|``str`is a monoid with`''`and`+`(boooo using`+`to combine strings! but whatever)

Tuple, Complex and Float are also monoids in exactly the ways you expect. There's a catch though: When combining tuples, you actually get a list back. I'll probably rectify this at a later point, but it's just something to live with right now.

`pynads` also defines [STRIKEOUT:two] three monoids of its own:
`List` (the monadic form of `tuple`), `Map` (the applicative form
of `dict`) and `Mempty` (which we're getting to!).

Making your own monoid is easy, and you've probably done it before (just
not in this fashion). Just inherit from `pynads.Monoid` create a
`mempty` attribute (it won't let you without it through `__new__`
hackery) and the `mappend` method for combining two instances of your
monoid. Let's assume we want a *real* tuple Monoid. We'd do it like
this:

from pynads import Monoid # also inherits from Container # so we get all the Container goodness for free class MTuple(Monoid): mempty = () def __init__(self, *vs): super(MTuple, self).__init__(vs) def mappend(self, other): # type checking optional if not isinstance(other, MTuple): raise TypeError("Can only mappend MTuple with MTuple") return MTuple(*(self.v + other.v)) def __repr__(self): return "MTuple{!r}".format(self.v)

print(MTuple(4,5) + MTuple(6,7))

MTuple(4, 5, 6, 7)

`pynads.Monoid` overrides `+` to be a shortcut to `mappend`.
That's all well and good, but *why* other than have a unified way of
combining values?! Because we get a way to reduce a list of monoids into
a single value for free!

print(MTuple.mconcat(MTuple(1), MTuple(2), MTuple(3)))

MTuple(1, 2, 3)

`Monoid.mconcat` actually delegates to the `mappend` method and
essentially looks like `reduce(cls.mappend, monoids)`. That's it.
That's all there is. But you can define your own `mconcat` to get
performace bonuses if you need to.

from itertools import chain class CMTuple(MTuple): def __iter__(self): return iter(self.v) @classmethod def mconcat(cls, *MTuples): return CMTuple(*chain.from_iterable(MTuples))

print(CMTuple.mconcat(CMTuple(1), CMTuple(2), CMTuple(3)))

MTuple(1, 2, 3)

`pynads.List` and `pynads.Map` take advantage of this to create only
one intermediate object rather than a bunch. `pynads` will also let
you treat the builtin types as monoids as well through
`pynads.funcs.monoid` namespace which has four functions we're
interested in: `mempty`, `mappend`, `mconcat` and `is_monoid`.
`mempty` returns the "zero" value for a type, `mappend` knows how to
combine types, `mconcat` knows how to combine an iter of types into a
single one and `is_monoid` knows if something is monoidal or not
(generally, it doesn't declare `decimal.Decimal` to be a monoid but
this is because I didn't want to add a special case -- special cases
beget special cases).

This is done through introspection of types and abstract base classes (to make the type introspection [STRIKEOUT:more acceptable] less painful).

from pynads.funcs import monoid print(monoid.mempty(list())) print(monoid.mappend({'a':10}, {'b': 7})) print(monoid.mconcat("hello", " ", "world")) print(monoid.is_monoid(set()))

[] {'a': 10, 'b': 7} hello world True

The monoid namespace is just a nice interface to the nasty plumbing that
lives in `pynads.utils.monoidal`. It's pretty gross and actually
probably pretty fragile, but it works! IT WORKS!!!

#### Mempty

So here's the thing that lets `Writer` do its little trick by accept
any monoid as a potential log. I can't know ahead of time what you're
going to use to keep track of stuff with `Writer` -- I've drank plenty
of spice water, but I've yet to develop prescient abilities. And rather
than making a bunch of subclasses specialized to handle a dictionary and
a list and a string and a blah blah blah and forcing you to make your
own for `WriterLogWithMyFirstMonoid` I decided to create a mempty
monoid -- `Mempty`. It's not an original idea. Really, it's kind of a
dumb object.

It's a singleton, so that's a strike against it (two singletons in my
library, my god!). It doesn't actually *do* anything. It just sits
around, taking up space until a real monoid comes along and scares it
off. It's mempty value is actually itself! It's mappend just returns
whatever its mappended with. And it's mconcat filters out any Mempty
values before trying to mconcat the remaining values (you get a Mempty
if mconcat an iter of Mempties). There's even an `__iter__` method
that yields from an empty tuple! What's going on!

In Haskell, `mempty` can be used a place holder and Haskell knows to
do the right thing already. However, we have to teach Python how to use
a placeholder and silently step it out of the way when a real value
comes along. I suspect that this is similar, maybe, to how Haskell
handles it, but I've not dug at all.

from pynads import Mempty print(monoid.mempty(Mempty)) print(Mempty + 4) print(monoid.mconcat(Mempty, {4}, {5}, Mempty))

Mempty 4 {4, 5}

So Writer's little trick of allowing any Monoid be the log is really just, "I have this dumb thing that pretends to be a Monoid as a default log."

### Helpers

I won't explore this section indepth because `pynads.funcs` has a
whole host of helpers, with more being added as needed. There's helpers
for making constants, composing functions, an identity function (as in
`lambda x: x` not `id(x)`), turning values into Monads. There's also
`pynads.utils` if you need to plumb some of the internal workings like
compatibility (did I mention all the tests pass 3.4 and 2.7?!) or how
monoids are determined on things that aren't explicitly monoids.

### That's it!

Well, not really. There's about five other monads implemented that I didn't cover (like Reader, which was a pain but also a lot of fun to implement). There's more coming as well. Not just monads, but other things. A proof of concept for do notation via abusing coroutines exists in the repo (lifted from a blog called Valued Lessons which served as partial inspiration for this project). And maybe Monad Transformers if I work the gumption up for it (probably yes). And who knows what else.

I spilled my brains, spill some of yours.