Symbol#to_proc helping out with #select to beat Scala-s solution

Hey!

I had a discussion with my colleague about Java lacking decent
collection API. This is indeed true. One of the main reasons is that
there’s no lambda and no closures in Java. The problem has been tried
to leverage with external libraries, but they’re failing in my mind.

Check out the following example in lambdaj [1]:
with(sales).retain(having(on(Sale.class).getValue(),greaterThan(50000))).extract(on(Sale.class).getBuyer()).sort(on
(Person.class).getAge());

It is just awful. So my colleague created the same example in Scala:
sales.filter(.value > 50000).map(.buyer).sortBy(_.age)

He also created it in Ruby:
sales.find_all{|sale| sale.value > 50000}.map{|sale|
sale.buyer}.sort_by{|buyer| buyer.age}

I polished it by using Ruby 1.9 syntax and find_all alias called
select:
sales.select{|sale| sale.value > 50000}.map(&:buyer).sort_by(&:age)

It’s still whopping 12 characters longer. So i decided to use
different block variable - more Scala-like underscore:
sales.select{|_| _.value > 50000}.map(&:buyer).sort_by(&:age)

Still 6 characters longer. Even if i’d be able to use syntax like
this, i’d win only 2 characters:
sales.select(&:value > 50000).map(&:buyer).sort_by(&:age)

My first question is, is there any meaningful way to shorten my
#select call by not creating any additional methods to my Sale class?
One solution would be to just create a method “good” and use it:
class Sale
def good
value > 50000
end
end

sales.select(&:good).map(&:buyer).sort_by(&:age)

I’d call it cheating though because the next step could be to use
alias_method to create one-character-length aliases to Enumerable
methods.

I’m thinking that one possible way to shorten my calls would be to use
instance-eval in #select, #map and #sort_by so i could do it like
this:
sales.select{value > 50000}.map{buyer}.sort_by{age}

Any other ideas how to beat a statically typed language with this
concrete example? :slight_smile:

[1] - http://code.google.com/p/lambdaj

Jarmo P.

On Fri, Feb 25, 2011 at 9:15 PM, Jarmo P. [email protected]
wrote:

It’s still whopping 12 characters longer. So i decided to use
different block variable - more Scala-like underscore:
sales.select{|_| _.value > 50000}.map(&:buyer).sort_by(&:age)

Still 6 characters longer. Even if i’d be able to use syntax like
this, i’d win only 2 characters:

What’s the point of this exercise? Why is “beating” Scala’s solution the
same as getting it shorter?

The original Ruby code and Scala’s are equally readable. Contrast their
readability to the lambdaj code you gave, and there’s no more to do.
Both
beat lambdaj without any greater expenditure of effort required. Hacking
things apart and introducing instance_eval selects is worse than using
character length!

That said, this does appeal to me:

array.select { &:value > 5 }

Perhaps something could be done with the singleton of the proc resulting
from Symbol#to_proc, to pass method calls to the block parameter. But
then
perhaps that’s just ugly.

Oh, crap. I already wrote a reply but it didn’t end up here for some
reason.

Anyway, the point was not to prove which is better or worse, but it
just got me thinking why is Ruby-s syntax more verbose and how would
it be possible to be improved. If at all. That’s all.

When analyzing the differences between Scala and Ruby in that concrete
example then i can see that Scala “wins” by automatically initializing
local variable “_” in the context of “block”. In Ruby we have to
specify “&:”. It would be already even better if we could drop the “&”
for invoking #to_proc. And the next step would be to drop “:” too.
Technically it should be possible if #select, #map and #sort_by
allowed to instance-eval. Of course it wouldn’t look good from the
inside, but it would definitely rock even more from the outside :slight_smile:

And when in a need to specify full block syntax in Ruby with pipes and
such then there is even more verbosity.

By the way, the example you wrote:
array.select { &:value > 5 }

Is not working unfortunately. I tried it also before. I tried it also
with parentheses instead of curly-braces. It would be awesome if it
worked though:
array.select(&:value > 5)

But why it doesn’t work though? Does “.>” have higher precedence over
“&”? How to force “&” have higher precedence so it could work?

Again, i was not trying to create any competition in here, just having
some random thoughts.

Jarmo

I tried to modify the methods directly in Enumerable and succeeded
only 50%.

Consider the code below:
module Enumerable
def select
puts 1
end
end

class Array
include Enumerable
end

[2,3].select # doesn’t print 1

If i use find_all instead then it works:
module Enumerable
def find_all
puts 1
end
end

class Array
include Enumerable
end

[2,3].find_all # prints 1

Now i’m confused as to why isn’t #select working as i was hoping for?

Jarmo

[snip]

I’m thinking that one possible way to shorten my calls would be to use
instance-eval in #select, #map and #sort_by so i could do it like
this:
sales.select{value > 50000}.map{buyer}.sort_by{age}

Here’s a trivial implementation of that idea:

module Relational
def project(&block)
map { |x| x.instance_eval(&block) }
end

def where(&block)
select { |x| x.instance_eval(&block) }
end

def order_by(&block)
sort_by { |x| x.instance_eval(&block) }
end
end

class Buyer < Struct.new(:name, :age)
end

class Sale < Struct.new(:value, :buyer)
end

alice = Buyer[“Alice”, 30]
bob = Buyer[“Bob”, 40]
charlie = Buyer[“Charlie”, 50]

sales = [
Sale[80000, alice],
Sale[40000, bob],
Sale[60000, charlie],
]

class Array
include Relational
end

p sales.where{ value > 50000 }.project{ buyer }.order_by{ age }

=> [#<struct Buyer name=“Alice”, age=30>, #<struct Buyer

name=“Charlie”, age=50>]

But it becomes uglier when you want to do this:

p sales.where{ value > 50000 }.project{ [value, buyer] }.order_by{
self[1].age }

=> [[60000, #<struct Buyer name=“Alice”, age=30>], [80000, #<struct

Buyer name=“Charlie”, age=50>]]

Better to do it this way round:

p sales.where{ value > 50000 }.order_by{ buyer.age }.project{ [value,
buyer] }

=> [[60000, #<struct Buyer name=“Alice”, age=30>], [80000, #<struct

Buyer name=“Charlie”, age=50>]]

However, in practice, I don’t use it. Ruby’s block syntax is perfectly
elegant as it is. Why not be content with it?

Regards,
Sean

Jarmo P. wrote in post #984005:

When analyzing the differences between Scala and Ruby in that concrete
example then i can see that Scala “wins” by automatically initializing
local variable “_” in the context of “block”.

Setting _ with block arguments is pretty ugly, especially if the block
takes multiple arguments (you’d get an array). It’s like Perl’s $_ which
Ruby unfortunately inherits for gets.

Your instance_eval solution changes the object context; code in your
block would not be able to use instance variables or methods of the
object to which the enclosing method belongs, which would be a major
stumbling block for anything other than the simple cases you give.

If you want to make the code shorter than scala, you could always alias
:s :select

:slight_smile:

I still don’t understand why modifying Enumerable didn’t work, but i
tried with Array instead and made this solution:
class Array
%w[select map sort_by].each {|m| class_eval %Q[alias_method :_#{m},
m; def #{m}█ _#{m} {|x| x.instance_eval &block} end]}
end

That allows me to write the example above like this:
sales.select{value > 50000}.map{buyer}.sort_by{age}

It’s not too bad, i think. I mean, it’s not less readable as it is
with regular block/&: syntax.

Jarmo

you should enjoy raganwald’s string#to_proc experiments

http://weblog.raganwald.com/2007/10/stringtoproc.html

martin

Good point about missing the binding for variables outside of the
block when using instance_eval :slight_smile:

Indeed, i could alias :s :select. I’m still wondering why aliasing
those methods in Enumerable didn’t help. It’s not even related to the
original topic, but i have a general interest as of why didn’t it
work…

Jarmo

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs