Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby's tap idiom in Python

There is a useful Ruby idiom that uses tap which allows you to create an object, do some operations on it and return it (I use a list here only as an example, my real code is more involved):

def foo
  [].tap do |a|
    b = 1 + 2
    # ... and some more processing, maybe some logging, etc.
    a << b
  end
end

>> foo
=> [1]

With Rails there's a similar method called returning, so you can write:

def foo
  returning([]) do |a|
    b = 1 + 2
    # ... and some more processing, maybe some logging, etc.
    a << b
  end
end

which speaks for itself. No matter how much processing you do on the object, it's still clear that it's the return value of the function.

In Python I have to write:

def foo():
  a = []
  b = 1 + 2
  # ... and some more processing, maybe some logging, etc.
  a.append(b)
  return a

and I wonder if there is a way to port this Ruby idiom into Python. My first thought was to use with statement, but return with is not valid syntax.

like image 837
Michał Kwiatkowski Avatar asked Sep 16 '10 09:09

Michał Kwiatkowski


3 Answers

Short answer: Ruby encourages method chaining, Python doesn't.

I guess the right question is: What is Ruby's tap useful for?

Now I don't know a lot about Ruby, but by googling I got the impression that tap is conceptually useful as method chaining.

In Ruby, the style: SomeObject.doThis().doThat().andAnotherThing() is quite idiomatic. It underlies the concept of fluent interfaces, for example. Ruby's tap is a special case of this where instead of having SomeObject.doThis() you define doThis on the fly.

Why I am explaining all this? Because it tells us why tap doesn't have good support in Python. With due caveats, Python doesn't do call chaining.

For example, Python list methods generally return None rather than returning the mutated list. Functions like map and filter are not list methods. On the other hand, many Ruby array methods do return the modified array.

Other than certain cases like some ORMs, Python code doesn't use fluent interfaces.

In the end it is the difference between idiomatic Ruby and idiomatic Python. If you are going from one language to the other you need to adjust.

like image 196
Muhammad Alkarouri Avatar answered Nov 13 '22 02:11

Muhammad Alkarouri


You can implement it in Python as follows:

def tap(x, f):
    f(x)
    return x

Usage:

>>> tap([], lambda x: x.append(1))
[1]

However it won't be so much use in Python 2.x as it is in Ruby because lambda functions in Python are quite restrictive. For example you can't inline a call to print because it is a keyword, so you can't use it for inline debugging code. You can do this in Python 3.x although it isn't as clean as the Ruby syntax.

>>> tap(2, lambda x: print(x)) + 3
2
5
like image 33
Mark Byers Avatar answered Nov 13 '22 03:11

Mark Byers


If you want this bad enough, you can create a context manager

class Tap(object):
    def __enter__(self, obj):
        return obj

    def __exit__(*args):
        pass

which you can use like:

def foo():
    with Tap([]) as a:
        a.append(1)
        return a

There's no getting around the return statement and with really doesn't do anything here. But you do have Tap right at the start which clues you into what the function is about I suppose. It is better than using lambdas because you aren't limited to expressions and can have pretty much whatever you want in the with statement.

Overall, I would say that if you want tap that bad, then stick with ruby and if you need to program in python, use python to write python and not ruby. When I get around to learning ruby, I intend to write ruby ;)

like image 7
aaronasterling Avatar answered Nov 13 '22 01:11

aaronasterling