views:

1056

answers:

7

I'm trying to come up with an elegant way of creating a list from a function that yields values in both Python and Ruby.

In Python:

def foo(x):
    for i in range(x):
        if bar(i): yield i 
result = list(foo(100))

In Ruby:

def foo(x)
  x.times {|i| yield i if bar(i)}
end
result = []
foo(100) {|x| result << x}

Although I love working in both languages, I've always been a bit bothered by the Ruby version having to initialize the list and then fill it. Python's yield results in simple iteration, which is great. Ruby's yield invokes a block, which is also great, but when I just want to fill a list, it feels kinda clunky.

Is there a more elegant Ruby way?

UPDATE Reworked the example to show that the number of values yielded from the function isn't necessarily equal to x.

+1  A: 

I know it's not exactly what you were looking for, but a more elegant way to express your example in ruby is:

result = Array.new(100) {|x| x*x}
Denis Hennessy
You're right, that's not quite what I'm looking for. The only reason I would be yield'ing from a method is if I didn't know exactly how many values would be yield'ed.
jcrossley3
+1  A: 
def squares(x)
  (0..x).map { |i| i * i }
end

Anything involving a range of values is best handled with, well, a range, rather than times and array generation.

womble
The example was contrived. The question pertains to any function that yields values, whether a range is involved or not.
jcrossley3
+8  A: 

So, for your new example, try this:

def foo(x)
  (0..x).select { |i| bar(i) }
end

Basically, unless you're writing an iterator of your own, you don't need yield very often in Ruby. You'll probably do a lot better if you stop trying to write Python idioms using Ruby syntax.

womble
Agreed. Both languages have a completely different approach to equivalent problems in a lot of cases.
bojo
You don't even need the to_a — Range includes Enumerable. Just (1..x).select {|i| bar i}.
Chuck
I wouldn't even call it a Python idiom, see the Python suggestions below.
FogleBird
+5  A: 

For the Python version I would use a generator expression like:

(i for i in range(x) if bar(i))

Or for this specific case of filtering values, even more simply

itertools.ifilter(bar,range(x))
list comprehensions ftw!
Ian Terrell
+1  A: 

For the Python list comprehension version posted by stbuton use xrange instead of range if you want a generator. range will create the entire list in memory.

Wayne
Not in python 3.0..
John Fouhy
+1  A: 

yield means different things ruby and python. In ruby, you have to specify a callback block if I remember correctly, whereas generators in python can be passed around and yield to whoever holds them.

recursive
IMO generators in python are a mess. They 'look' like functions but are nothing like them --- and you have to search through the function looking for the 'yield' before you can identify them as generators rather than functions. And since generator 'functions' return generator objects anyway...why not just make the API explictly about building the generator objects like it works in Ruby? It seems Ruby is more explicit here than Python -- with python trying to disguise them as functions (which is completely the wrong abstraction).
banister
@banister: I had a different reaction to the difference. As far as I know about ruby, (not very far) an equivalent construct to ruby's generators can be accomplished by simply accepting a function reference parameter, and calling it successively with each "generated" value. This only requires first class functions, which Python also has. The python approach uses continuations, which gives something deeper. The magic syntax around `yield` shouldn't be too significant for developers, since it's also possible to return generator objects from regular functions, like with generator comprehensions.
recursive
@recursive, i dont' quite understand how the equivalent construct in python works, could you give an example? FOr the record Ruby generators are implemented using fibers, which are fully-fledged language supported co-routines. Also, ruby has first class continuations :P
banister
@banister: I could be wrong about this. The last time I looked at ruby was pre-1.9.1, and I'm not totally sure even then, but my understanding of ruby generators is that were required to accept a function reference or something similar. Each generated value would be passed to this referenced function one at a time. By contrast, in python, generators work more like lazy iterators by default. I was unaware that ruby had continuations. It's definitely possible that I'm being unfair to ruby.
recursive
@recursive, ah...yes you're talking about ruby's internal iterators: `array.each { |i| puts i}`. They take a block (an anonymous function) and work as you say. However I was talking about `Enumerators` which are external iterators; they are implemented using Fibers and work like lazy iterators too. see here: http://gist.github.com/596582 The two approaches seem equivalent, the difference being that ruby makes the creation of generator objects explicit through the use of `Enumerator.new`, whereas python does it implicitly using what looks like a function definition.
banister
@banister: Ok, I think I get it now. It is kind of a strange reversal of roles, given python's philosophy of external is better and magic is bad. I think that personally, I am ok with both approaches though, now that I understand them.
recursive
+2  A: 

The exact equivalent of your Python code (using Ruby Generators) would be:

def foo(x)
    Enumerator.new do |yielder|
        (0..x).each { |v| yielder.yield(v) if bar(v) }
    end
end

result = Array(foo(100))

In the above, the list is lazily generated (just as in the Python example); see:

def bar(v); v % 2 == 0; end

f = foo(100)
f.next #=> 0
f.next #=> 2
banister