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

J

Jarmo Pertman

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? :)

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

Jarmo Pertman
 
A

Adam Prescott

[Note: parts of this message were removed to make it a legal post.]

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.
 
J

Jarmo Pertman

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 :)

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

[Note:  parts of this message were removed to make it a legal post.]

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.
 
S

Sean O'Halpin

[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
 
J

Jarmo Pertman

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
 
J

Jarmo Pertman

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}&block; _#{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

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
 
B

Brian Candler

Jarmo Pertman 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

:)
 
J

Jarmo Pertman

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

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,968
Messages
2,570,149
Members
46,695
Latest member
StanleyDri

Latest Threads

Top