Slinkie

What is this?

I really like list comprehensions. They're very powerful tools, but they have a problem in that they don't stack well. If you want to do something like putting a comprehension inside a comprehension, the code probably isn't going to be very comprehensive anymore.

You could store intermediate results in variables, and that can be very readable, but for this type of problem I tend to prefer method chains.

The Slinkie library aims to add these method chains to Python 3.5 and up. It's written with LINQ and JS in mind, and aims to look and feel mostly the same. An important difference to JS is that that it's lazy (just like LINQ).

How do I install it?

It's available for Python 3.5 and up on PyPI.

pip install slinkie

Import it to your project by saying

from slinkie import Slinkie

Examples

In a lot of languages, for example JavaScript, it's possible to say things like this:

animals = [lassie, skippy, scooby, cerberus, garfield]

// Find all the good dogs.    
good_dogs = animals
    .filter(animal => animal.type == 'dog')
    .filter(dog => dog.is_good);

// Pat the good dogs.
good_dogs.map(dog => dog.pat());

If you just wanted to pat all the good dogs, you could say something like this:

// Find and pat all the good dogs.    
animals
    .filter(animal => animal.type == 'dog')
    .filter(dog => dog.is_good)
    .map(dog => dog.pat());

With Slinkie, the same thing would look something like this:

from slinkie import Slinkie

animals = [lassie, skippy, scooby, cerberus, garfield]

(
    Slinkie(animals)
        .filter(lambda animal: animal.type == 'dog')
        .filter(lambda dog: dog.is_good)
        .map(lambda dog: dog.pat())
        .consume()
)

You'll notice a few differences. The parentheses around the whole expression allows us to write it over multiple lines. Without them, this would be considered a syntax error. Next, you'll notice that Python's lambdas are different from those in both JS and LINQ. You should read up on them if you're not familiar, because the differences are important. The third thing you'll notice is the call to consume() at the very end of the chain. This is because Slinkie is lazy. Consume tells the Slinkie to consume all the elements in the list, and actually do something with them. If you left it out, nothing would actually happen.

Consume is also special in that it doesn't return anything. There are other functions you can call to get, for example, a list or a set back:

good_dogs = (
    Slinkie(animals)
        .filter(lambda animal: animal.type == 'dog')
        .filter(lambda dog: dog.is_good)
        .list()  # or .set(), .tuple(), or .dict(...). 
)

You can also use it as a generator. (Because it actually is a generator).

# Considering Python's rather lacking lambdas,
# it's often nicer to define a small function.
def is_dog(animal):
    return animal.is_dog

for dog in Slinkie(animals).filter(is_dog):
    print(dog.name, 'is a dog.')

For now, I encourage you to look at the unit tests for more toy examples.

For the LINQ developers out there, there are aliases for a number of methods. select can be used in place of map, where can be used in place of filter, etc. SelectMany doesn't have a counterpart, instead you should use flatten.