DEV Community

loading...

Python Slices vs Ruby blocks

redfred7 profile image Fred Heath ・2 min read

A couple of my Python colleagues tried to impress me today with Python's named slices feature. The way it works is like that:

 s = list('helloworld!')
  => ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd', '!']
WORLD = slice(5, 10)
s[WORLD]
 => ['w', 'o', 'r', 'l', 'd']
Enter fullscreen mode Exit fullscreen mode

So you can have your own customised slicing mechanism that you can apply to any list. Which is kind of cool. That prompted me to demonstrate how we can do the same thing with Ruby. Luckily, in Ruby world we have blocks, procs and lambdas, the ultimate play-dough that allows us to stretch and flex in every direction.

s = ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd', '!']
  => ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd', '!']
world = proc {|x | x.slice(5, 10)}
world[s]
 => ["w", "o", "r", "l", "d", "!"]
Enter fullscreen mode Exit fullscreen mode

​so we can do things like:

first = proc { |x| x.slice(0) }
first[s]
=> "h"
Enter fullscreen mode Exit fullscreen mode

or even things that we can't do with Python's named slicing, since it doesn't allow us to pass the receiver as an argument to the block (x in the example below)

last = proc {|x| x.slice x.length-1, x.length}
last[%w(dog rabbit fox cat)]
=> ["cat"]
Enter fullscreen mode Exit fullscreen mode

or

median = proc {|x| x.slice(x.length / 2) }
median[%w(dog rabbit cat)]
=> "rabbit"
Enter fullscreen mode Exit fullscreen mode

and of course we're not just restricted to slicing arrays,

domain_extractor = proc { |x| x.gsub(/.+@([^.]+).+/, '\1') }
domain_extractor["fred@mydomain.co.uk"]
=> "mydomain"
Enter fullscreen mode Exit fullscreen mode

and since a block is just an anonymous Proc object, we can use it with any method that accepts Proc parameters

email_list = ["fred@mydomain.com", "john@gmail.com", "mary@yahoo.co.uk"]
=> ["fred@mydomain.com", "john@gmail.com", "mary@yahoo.co.uk"]
email_list.map(&domain_extractor)
=> ["mydomain", "gmail", "yahoo"]
Enter fullscreen mode Exit fullscreen mode

Blocks and Procs (a.k.a lambdas) are ideal for some quick, reusable functionality or for when defining a full-blown method would be too much, but also for more serious uses such as callbacks and deferred execution. IMHO, they are a fundamental part of what makes Ruby such a flexible and powerful language. Learn them, use them, enjoy them :)

Discussion (4)

pic
Editor guide
Collapse
nestedsoftware profile image
Nested Software • Edited

I suppose in python, equivalents would be something like below.

world = lambda x: x[5:10]
world(list('helloworld'))
['w', 'o', 'r', 'l', 'd']

last = lambda x: x[len(x)-1:] 
last('dog rabbit fox cat'.split())
['cat']

median = lambda x: x[len(x)//2]
median('dog rabbit cat'.split())
'rabbit'

If it's really important to be able to apply the same slice to a bunch of different lists, I guess that the python slice function is concise, but I agree with you that just using a lambda seems to give more flexibility, and it is still pretty concise.

Collapse
redfred7 profile image
Fred Heath Author

Great equivalent examples in Python, thank you! The difference of course is that Python lambdas are somewhat limited to a single expression, no conditionals and such, AFAIK. This makes them less flexible than Ruby lambdas.

Also, in Python lambdas are a fairly recent addition, almost an afterthought. In Ruby they are embedded in the core of the language since its inception. Most Python programmers hardly ever use lambdas and they can perform their tasks just fine without them. Ruby coders on the other hand just couldn't work without lambdas. Literally, as core language functionality relies on them :)

Collapse
nestedsoftware profile image
Nested Software • Edited

I guess I am okay with python's one-line lambdas, since it's easy enough to write a normal function if the logic required doesn't fit - their properties are otherwise the same. Whether one loves or hates the use of indentation to denote blocks in python, it does make regular functions in python pretty concise already:

last = lambda x: x[len(x)-1:]

# vs.

def last(x): 
    return x[len(x)-1:] 

# both versions are clear and short I think...

In general I am not a big fan of anonymous code blocks, so limiting them to short snippets of code seems acceptable to me.

It looks as though regular functions in ruby don't act as closures, so you need blocks, procs, or lambdas for that. In python, and javascript for that matter, normal functions will capture the enclosing scope, so I think that's probably why the aforementioned code blocks are so essential in ruby, but not really so significant in python. My gut feeling is that python's approach is more orthogonal, therefore better. I don't know what the considerations were in the design of ruby though.

I kind of dislike both python and ruby's lambdas a bit though, because they both require special syntax. I'm not enough of an expert to deeply understand the whole situation, but in principle, I do like the idea of using the same syntax to define functions everywhere. It works in javascript for example:

function delayedHello(name) {
  setTimeout(function() {
    console.log(`Hello, ${name}!`)
  }, 1000)
}

Sadly, in spite of this, javascript still has the problem of way too many ways to write functions! Regular functions, arrow functions, class methods, properties transform... sigh...

Thread Thread
redfred7 profile image
Fred Heath Author

I didn't know that Python functions were also closures so thanks for that! And yes, Ruby functions (methods) are scope gates, the scope changes as soon as you enter them so they're not closures.

I suppose it comes down to different programming philosophy and approach between Ruby and Python. If you love well-structured, one-way-of-doing-it approaches then Python is for you. Ruby is more loose in that sense but that's why I (and many others) find it so attractive.

As for JavaScript, well, don't get me started :D