Skip to content
S Leibrock edited this page Jan 12, 2016 · 3 revisions

FAQ

What brought this project about?

I always hated Python's composition system. I don't like writing nested functions because they usually don't ever look good, and I tend to write a lot of nested functions, because it's better than writing new functions that simply did it anyway, but kept it hidden and tucked away. I hate cluttering namespaces with garbage like that.

I also liked the code that gets used in SQL Alchemy, stuff like this is pretty cool, and it's a shame that main Python will never actually be like this ever.

>>> our_user = session.query(User).filter_by(name='ed').first() 

Why re-write Python built-in functions?

When I was writing this in my sleep-deprived state, I forgot to account for certain interactions between Units and functions. For instance, if you have a Unit of an integer, but you call length on it anyway. It's technically one element, so why not have it just say that?

range, filter and map were also subject to weird interactions with Units. In Python 3, all three of them were changed to return iterator objects called generators which would produce results upon request as opposed to when the function was returned.

So in normal Python, this works:

filter(odd, range(10))
# => <filter at 0x12341234>

That means filter clearly takes a range iterator. But:

Unit(10) | range | select(odd)
# does NOT work

It's a weird problem and I think it has something to do with how Python 3 range now works. But I said "screw it, I didn't like it anyway" and wrote some stuff that works better in my favor.

So instead of doing this:

Unit(10) | range | list | select(odd)

We can just do this by using span, which creates a list directly.

Unit(10) | span | select(odd)

range's functionality is kept in the function to, as being able to start from a certain number is useful.

What about fmap and select?

So, back to range, filter and map all being different and posing unique challenges.

In this composition method, Unit will continue to store all computations of functions and dumping the previous calculation. Meaning it can only hold as many values as it was given by the last one.

Let's say I wanted to do a "filter-map", which is just a filter op right after a map, something commonly done with List comprehensions. The problem here is that map only returns one value after using two, and to chain into filter, we need two values, not just one.

For now let's just store the two arguments we want to use inside of a Unit and pass it to map.

Unit(succ, range(10)) | map | list
# => Unit([1,2,3,4,5,6,7,8,9,10])

Great! Now how do we go about filtering this info?

# Can't do something like this because filter isn't curried!
Unit(succ, range(10)) | map | list | filter(odd)

# Instead you'd have to store the value of the Unit and 
# do another statement right after
mapres = Unit(succ, range(10)) | map | list | True
filted = Unit(odd, result) | filter | list | True

It's hard to interject another value in between Unit operations, so it was easier to curry filter and map and rename both of them to something different (fmap because it's roughly the same idea as Haskell's fmap, and select because of Ruby).

Unit(10) | span | fmap(succ) | select(odd)
# => Unit([1,3,5,7,9])

Much nicer, don't you think?

Why is it necessary to pass "True"?

So Unit captures all data that's returned as a result from function composing. My first, original idea was to create an "id" function which would return exactly what was put in - yet that didn't work. I forgot to take into account that even if you "returned self", it would not escape Unit's data encapsulation. We needed a data-jailbreak method defined at the class-level instead of relying on passed functions.

So I thought, "hey, True is a good keyword constant for this kind of thing, right?" and thus I decided that if you pass an expression True, you clearly intend for something to happen. I also had the idea if you pass it a False, the expression should be terminated and result in nothing. This was a kind of weird idea I thought of when I was thinking about implementing Maybe in Python (since Maybe a = Just a | Nothing, if a Just interacts with a Nothing, the expression results in Nothing).

The plus side is that you can use the end expression as a cool little "if" statement for flexible kinds of programming. I'm not going to say for WHAT because I have no idea, but the option is there.

No Monads?

I gave it a long shot and tried making a kind of "Monad" system in Python. I even drew inspiration from PyMonad, which is a really cool library that continues to impress me.

The problem boils down to something that makes it hard to justify even wanting to use. A Monad's number one use is the use of the bind operation, >>=. Bind states simply:

bind :: Monad m => m a -> (a -> m b) -> m b

It takes a Monad with a value A, a function that takes A and returns a Monad of B, and applies the function to create a new Monad with a value of B. It might not make a whole lot of sense, but that's Haskell for most people.

Within PyMonad, the author was able to basically create what Haskell has done with their Monad system. However, Haskell was able to do something that we can't do in Python - change the syntax.

Here is a basic Monadic expression in PyMonad:

Just(9) >> (lambda x: Just(8) >> (lambda y: Just(x + y)))

It's a bunch of nested lambda statements. Yuck!

Haskell was able to avoid this by creating a cool little notation called the "do" notation, which simply expands to that nested lambda format above.

foo = do  
    x <- Just 9  
    y <- Just 8  
    Just (x + y) 

So obviously there's no real way we can do that in Python. We can't add syntax to the language ourselves, not without changing much of the language entirely. I think the Unit expressions I have are a more direct and quick way of expressing computations without taking too much of the user's time to set it up.

As much as I love the idea of Monads, I don't think I can see anyone using them for anything realistic right now.

What the heck is the "collect" function?

This is an experiment, in all honesty. I had an idea to do some filters on top of a list of random numbers from random.random. But I realized my current system has no way of actually collecting the results of this function several times. It takes no arguments and it's completely impure as it can't be inverted. What can one man do?

There's a small set of functions that are also impure, mostly to do with IO-actions, like input. input can take optionally take in a string, but you can also use it without any arguments. It also can't be inverted, has side-effects, so it's also impure.

So I decided to create "collect", a function that takes in a number saying how many times it wants to call a function with no arguments. It's good for getting me random numbers, that's for sure. Not much else I can see right now though...

from random import random
Unit(random) | collect(10000)
# => 10k random numbers