Any solutions to Ruby's inconsistencies?


#1

Ruby has definite positives over Java and even other scripting
languages, but many negatives too. One negative is the inconsistency of
how rules seem to be applied in the language and accompanying toolsets.
Here are a few that have bitten me:

  1. I almost immediately got started with Ruby on Rails and loved the
    idea of partials, but then I found that the ||= idiom doesn’t work on
    partials’ parameters.

<% my_param_a ||= 1 # Errors out
my_param_a = my_param_a ? my_param_a : 1 # works
-%>

That seemed odd as I thought both were equivalent, and it broke some of
the trust I had for Ruby. Not as much as early Rails’ use of 2 separate
variables for RAILS_ENV, but…

  1. Then when using blocks to process an array I tried to use another
    Ruby control structure:

arr=[1,2,3,4]
arr2 = arr.collect{|ii| return 0 if ii == 3; ii+1}

result: LocalJumpError: unexpected return

That also caught me off guard - I need the block to return a value, to
evaluate to a value - but suddenly I’m told that ‘return’ doesn’t mean
what I think it means? At least not inside a block (or Proc.new)…

  1. Originally I was taught that symbols were the salvation of memory and
    key problems - and they certainly sounded like it. Up until the point a
    few days later when one (Rails?) map used Symbols and another used
    Strings. Ouch.
    Without any explicit typing abilities in the language (optional or
    otherwise?) it seems like the only solution is to return to basic string
    matches:
    regex = Regexp.compile("^#{matcher.to_s}$", Regexp::IGNORECASE)
    key = map.keys.find{ |key| key.to_s =~ regex }
    Yuck.

  2. And what’s up with Ruby incorrectly naming their Map ‘Hash’? Or is
    my C.S. idea of a Hash too limited? Granted in Java they have HashMap,
    but if you enter one value and get a second one out it is a Map,
    regardless of how the keys are stored. A ‘Hash’ would instead be a type
    of set.

Please let me know how I can supplement my possibly deficient Ruby
education :slight_smile: Relevant discussions or sections of “The Ruby Way”
appreciated!

-Chris


#2

On Mar 4, 10:02 am, “C. Dagnon” removed_email_address@domain.invalid wrote:

  1. Originally I was taught that symbols were the salvation of memory and
    key problems - and they certainly sounded like it. Up until the point a
    few days later when one (Rails?) map used Symbols and another used
    Strings. Ouch.
    Without any explicit typing abilities in the language (optional or
    otherwise?) it seems like the only solution is to return to basic string
    matches:
    regex = Regexp.compile("^#{matcher.to_s}$", Regexp::IGNORECASE)
    key = map.keys.find{ |key| key.to_s =~ regex }
    Yuck.

There’s been endless discussion and confusion about Symbols, Strings,
what they mean, what they should be used for, and their relation to
each other. Rails did not help with HashWithIndifferentAccess, though
I imagine they thought they would. But I don’t want to start anything
like that up. I just want to point out that a lot of that code is
unnecessary and could just be:

key = map.keys.detect { |k|  k.to_s.downcase ==

matcher.to_s.downcase }

Specifically, “#{something.to_s}” is not required because string
interpolation calls to_s on the object. And anyway, this example seems
confused with Symbol vs. String as well as case-insensitive matching.
If you want something like that, you write your own Hash (or Map)
subclass or find something someone else wrote. Maybe there’s
HashWithReallyIndifferentAccess or HashThatJustKnowsWhatYouMean or
HashThatWillGiveYouTheValueOfSomeTangentionallyRelatedKey.

  1. And what’s up with Ruby incorrectly naming their Map ‘Hash’?

This isn’t only a Ruby thing. I’m used to it, but then again I came to
Ruby from Perl.

At least they’re not called “dictionaries”, right? Am I right?

Or is my C.S. idea of a Hash too limited? Granted in Java they
have HashMap, but if you enter one value and get a second one
out it is a Map, regardless of how the keys are stored.

Hey, why aren’t Arrays called Maps instead?


#3

Or is my C.S. idea of a Hash too limited? Granted in Java they
have HashMap, but if you enter one value and get a second one
out it is a Map, regardless of how the keys are stored.

Hey, why aren’t Arrays called Maps instead?

I’ve always found Java’s naming of collections to be contrary to its
idea of
OO in that it exposes implementation details, e.g. HashSet,
LinkedHashSet
and TreeSet really ought to be called Set, OrderedSet and SortedSet to
reflect their intended use rather than their internal structure.

‘Map’ is unfortunately too vague a term and could be used to refer to
anything that maps a set of inputs to another set of outputs. You
arguably
call arrays, lambdas, methods and even objects ‘maps’. HashMap actually
isn’t a bad name for this since it differentiates the data structure
from
other mapping objects. I don’t mind ‘table’ or ‘hashtable’. ‘Dictionary’
is
a little restrictive in that it seems to imply string-only keys and
seems to
relate to JavaScript-style objects rather than real hashtables. All
said,
Hash isn’t bad: it’s an object that maps input to output by hashing the
input, which is all you need to know really.


#4

On Mar 4, 2009, at 5:02 PM, C. Dagnon wrote:

partials’ parameters.

<% my_param_a ||= 1 # Errors out
my_param_a = my_param_a ? my_param_a : 1 # works
-%>

That seemed odd as I thought both were equivalent, and it broke some
of
the trust I had for Ruby. Not as much as early Rails’ use of 2
separate
variables for RAILS_ENV, but…

What was the exact error? I don’t know how ERB in Rails work, but
sometimes those constructs are method calls and not variables.

  1. Then when using blocks to process an array I tried to use another
    Ruby control structure:

arr=[1,2,3,4]
arr2 = arr.collect{|ii| return 0 if ii == 3; ii+1}

result: LocalJumpError: unexpected return

That also caught me off guard - I need the block to return a value, to
evaluate to a value - but suddenly I’m told that ‘return’ doesn’t mean
what I think it means? At least not inside a block (or Proc.new)…

“return” in Ruby has a lexical meaning and can jump to the end of a
scope (the end of the method). In some contexts, that is not allowed.
Especially in the case of collect, that has to return an array that is
as large as the first one.

It is not inconsistent: it is just not Javas return. So the language
is right to tell you that it is not what you think it means: because
it ain’t.
Don’t use it unless you really want to jump.

I for my part find Ruby a rather consistent language. It’s just that
the ruleset is not simple.

Regards,
Florian


Florian G.

smtp: removed_email_address@domain.invalid
jabber: removed_email_address@domain.invalid
gpg: 533148E2


#5

On Wed, 04 Mar 2009 11:02:36 -0500, C. Dagnon wrote:

<% my_param_a ||= 1 # Errors out
my_param_a = my_param_a ? my_param_a : 1 # works
-%>

That seemed odd as I thought both were equivalent, and it broke some of
the trust I had for Ruby. Not as much as early Rails’ use of 2 separate
variables for RAILS_ENV, but…

I don’t use rails, and I don’t know what a partial is, so I can’t
answer.
You probably better ask on a Rails list.

  1. Then when using blocks to process an array I tried to use another
    Ruby control structure:

arr=[1,2,3,4]
arr2 = arr.collect{|ii| return 0 if ii == 3; ii+1} # result:
LocalJumpError: unexpected return

That also caught me off guard - I need the block to return a value, to
evaluate to a value - but suddenly I’m told that ‘return’ doesn’t mean
what I think it means? At least not inside a block (or Proc.new)…

A block (except when it’s a lambda) is meant to be looked at as though
it’s a control structure in the function, the same as if it were an
ordinary loop. So the idea of return is to return from the function.
Use next to return a value early from the block.

arr2 = arr.collect{|ii| next 0 if ii == 3; ii+1}

  1. And what’s up with Ruby incorrectly naming their Map ‘Hash’? Or is
    my C.S. idea of a Hash too limited? Granted in Java they have HashMap,
    but if you enter one value and get a second one out it is a Map,
    regardless of how the keys are stored. A ‘Hash’ would instead be a type
    of set.

Perhaps they should, but it would be a pain to change it now.

–Ken


#6

On Wed, 04 Mar 2009 12:01:27 -0500, James C. wrote:

I’ve always found Java’s naming of collections to be contrary to its
idea of OO in that it exposes implementation details, e.g. HashSet,
LinkedHashSet and TreeSet really ought to be called Set, OrderedSet and
SortedSet to reflect their intended use rather than their internal
structure.

No, no, no. In Java, you have the interfaces Set, and SortedSet (no
OrderedSet, I suppose it would have exactly the same operations as a
Set), which don’t define any operations, and AbstractSet which is a
helper class for people implementing sets.

Then you create instantiations HashSet, LinkedHashSet, TreeSet, which
you
instantiate knowing what data you intend to use it for, and which
behind-the-scenes implementation has the best performance
characteristics
for that data. (You should be allowed that choice, in general.) Then
you pass those around using the aforementioned implementation-agnostic
interfaces.


#7

Hi –

C. Dagnon wrote:

<% my_param_a ||= 1 # Errors out
my_param_a = my_param_a ? my_param_a : 1 # works
-%>

That seemed odd as I thought both were equivalent, and it broke some of
the trust I had for Ruby. Not as much as early Rails’ use of 2 separate
variables for RAILS_ENV, but…

I can’t duplicate that problem. What error message are you getting?

  1. Originally I was taught that symbols were the salvation of memory and
    key problems - and they certainly sounded like it. Up until the point a
    few days later when one (Rails?) map used Symbols and another used
    Strings. Ouch.
    Without any explicit typing abilities in the language (optional or
    otherwise?) it seems like the only solution is to return to basic string
    matches:
    regex = Regexp.compile("^#{matcher.to_s}$", Regexp::IGNORECASE)

You can use some shortcuts there:

/^#{matcher}$/i

 key = map.keys.find{ |key| key.to_s =~ regex }

Yuck.

In 1.9, symbols can be matched against regular expressions. So you can
do:

[:a,:b,:c,:cat].grep(/a/)

I have mixed emotions about the string-like behavior of symbols in 1.9
(they have methods like #upcase and #succ), but it has the practical
advantage of making it unnecessary to worry about which you’ve got if
you’re matching patterns.

Please let me know how I can supplement my possibly deficient Ruby
education :slight_smile: Relevant discussions or sections of “The Ruby Way”
appreciated!

There’s a book or two due out soon… see below :slight_smile:

David


David A. Black / Ruby Power and Light, LLC
Ruby/Rails consulting & training: http://www.rubypal.com
Coming in 2009: The Well-Grounded Rubyist (http://manning.com/black2)

Ruby Training Atlanta! April 1-3, http://www.entp.com/training/atlanta09


#8

On Wed, Mar 4, 2009 at 2:32 PM, C. Dagnon
removed_email_address@domain.invalidwrote:

Yossef M. wrote:

key = map.keys.detect { |k|  k.to_s.downcase == matcher.to_s.downcase

}

Hmmm, what is ‘detect’?

I had been doing that, but then I thought that compiling a single regex
would be less expensive than downcasing everything every time they got
compared.

You can also use String#casecmp to avoid making a new downcased string
object each time you do the comparison, e.g.

matcher.casecmp(k.to_s).zero?


#9

Yossef M. wrote:

There’s been endless discussion and confusion about Symbols, Strings,
what they mean, what they should be used for, and their relation to
each other. Rails did not help with HashWithIndifferentAccess, though
I imagine they thought they would. But I don’t want to start anything

I’m happy I didn’t miss anything and sad to hear I’m not alone in that
confusion.

like that up. I just want to point out that a lot of that code is
unnecessary and could just be:

key = map.keys.detect { |k|  k.to_s.downcase ==

matcher.to_s.downcase }

Hmmm, what is ‘detect’?

I had been doing that, but then I thought that compiling a single regex
would be less expensive than downcasing everything every time they got
compared. Not that the example I included appears in any of my code,
but I’ve been doing it for similar situations to allow for things like
case-indifferent parameters.

  1. And what’s up with Ruby incorrectly naming their Map ‘Hash’?

    At least they’re not called “dictionaries”, right? Am I right?

Yes, definitely right on that one.
Okay, so that was a Perl influence. Darn.


Hey, why aren’t Arrays called Maps instead?

Sure arrays can be considered special kinds of maps. The ordering
problem could be solved by my_map.keys.sort.each instead of
my_array.each. The major difference I see is that Arrays have
type-checked and validated keys (non-negative integers), though I would
also say there’s an assumption in how I’ve gotten used to using arrays
that if there is an element 5, there are elements 0-4 (even if nil).

Thanks for the reply!

-Chris


#10

On Mar 4, 3:32 pm, “C. Dagnon” removed_email_address@domain.invalid wrote:

key = map.keys.detect { |k|  k.to_s.downcase ==

matcher.to_s.downcase }

Hmmm, what is ‘detect’?

‘detect’ is a synonym for ‘find’ (or maybe it’s the other way around).

I prefer to use the *ect methods when working with Enumerables instead
of the simpler/shorter synonyms. This is in part because they get some
consistency and connection, but also because Rails overrides some of
them on Enumerable-like association proxies. And some of them don’t
even have non-ect synonyms.

[].detect -> [].find
[].select -> [].find_all
[].reject ->
[].collect -> [].map
[].inject ->


#11

El Miércoles, 4 de Marzo de 2009, C. Dagnon escribió:

I had been doing that, but then I thought that compiling a single regex
would be less expensive than downcasing everything every time they got
compared.

Let’s do some benchmarks:


irb> aaa=“HolaQueTAL”
irb> bbb=“holaQUEtal”

irb> Benchmark.realtime { aaa.downcase == bbb.downcase }
1.97887420654297e-05

irb> Benchmark.realtime { aaa.casecmp(bbb) }
1.59740447998047e-05

irb> Benchmark.realtime { aaa =~ /^#{bbb}$/i }
3.19480895996094e-05

Did you know the String#casecmp method? It seems to be the faster way.


#12

Iñaki Baz C. wrote:

El Miércoles, 4 de Marzo de 2009, C. Dagnon escribió:

I had been doing that, but then I thought that compiling a single regex
would be less expensive than downcasing everything every time they got
compared.

Let’s do some benchmarks:

Did you know the String#casecmp method? It seems to be the faster way.

Nope, didn’t hear about that, nor Benchmark.realtime. Thanks - these
look useful!

My only suggestion would be that we try it against 100, 10,000, and
larger sets of strings to see which is faster - this is over a
collection after all. I wasn’t assuming a Regexp.compile would be
faster than other things for a single string compare.


#13

On Thu, Mar 5, 2009 at 11:52 AM, C. Dagnon
removed_email_address@domain.invalidwrote:

While I think I can find a use for reject and
inject, select doesn’t make much sense to me. I’ll have to see if I can
fit that into my mindmap…

Think of it selecting the elements of the enumerable for which the block
is
‘true’.

Another analogy is to an SQL SELECT statement with the block taking the
part
of the WHERE clause.

The *ect methods are evidence of the influence of Smalltalk on Ruby,
particularly inject, which is a Smalltalk variant on the more common
reduce
operation which allows injection of an initial value outside of the
receiver
into the sequence.

Ruby 1.9 adds the alias :reduce to Enumerable#inject.

Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale


#14

Yossef M. wrote:

On Mar 4, 3:32�pm, “C. Dagnon” removed_email_address@domain.invalid wrote:

� � key = map.keys.detect { |k| �k.to_s.downcase ==
matcher.to_s.downcase }

Hmmm, what is ‘detect’?

‘detect’ is a synonym for ‘find’ (or maybe it’s the other way around).

I prefer to use the *ect methods when working with Enumerables instead
of the simpler/shorter synonyms. This is in part because they get some
consistency and connection, but also because Rails overrides some of
them on Enumerable-like association proxies. And some of them don’t
even have non-ect synonyms.

[].detect -> [].find
[].select -> [].find_all
[].reject ->
[].collect -> [].map
[].inject ->

Interesting… I hadn’t learned about the *ect’s. I have used collect
(never map, yuck), and now that I see it I think detect is a better word
for find as used here. While I think I can find a use for reject and
inject, select doesn’t make much sense to me. I’ll have to see if I can
fit that into my mindmap…

Thanks for that explanation Yossef!


#15

On Thu, Mar 5, 2009 at 10:52 AM, C. Dagnon removed_email_address@domain.invalid
wrote:

Interesting… I hadn’t learned about the *ect’s. I have used collect
(never map, yuck),

Hmm, let me think about that method name… you are “mapping” a set of
things into another set (I’m using “set” for a simple group of
things); “collect” makes much less sense to me. What exactly are you
collecting? Probably just vernacular used in CS circles that I’m not
aware of, but silly nonetheless.

Todd


#16

On Thu, Mar 5, 2009 at 4:41 PM, Yossef M. removed_email_address@domain.invalid
wrote:

consistency and connection, but also because Rails overrides some of
them on Enumerable-like association proxies. And some of them don’t
even have non-ect synonyms.

[].detect -> [].find
[].select -> [].find_all
[].reject ->
[].collect -> [].map
[].inject ->

Interesting. Now that I think about which versions I use, I don’t have
a rule :-). I use:

find
select
reject
map
inject

So, no consistency or anything. Just the ones that I like better, I
suppose :-D.

Jesus.


#17

Todd B. wrote:

On Thu, Mar 5, 2009 at 10:52 AM, C. Dagnon removed_email_address@domain.invalid
wrote:

Interesting… I hadn’t learned about the *ect’s. I have used collect
(never map, yuck),

Hmm, let me think about that method name… you are “mapping” a set of
things into another set (I’m using “set” for a simple group of
things); “collect” makes much less sense to me. What exactly are you
collecting? Probably just vernacular used in CS circles that I’m not
aware of, but silly nonetheless.

I suspect it is from Common Lisp’s loop:

(loop for i from 1 to 3 collect (* i i))
==> (1 4 9)

You can do a bazillion different things inside a CL loop; collecting
results is just one of them. The loop terminology doesn’t really fit
here.

I prefer ‘map’ since that is the name for the operation in other
languages (including Lisp).


#18

On Thu, Mar 5, 2009 at 3:24 PM, Eleanor McHugh
removed_email_address@domain.invalid wrote:

things); “collect” makes much less sense to me. What exactly are you
collecting? Probably just vernacular used in CS circles that I’m not
aware of, but silly nonetheless.

Collect makes much more sense to me than map as I’m collecting a bunch of
results and storing them in an array. But the great thing about Ruby is that
whichever makes sense, you can use it - and if neither makes sense you can
add your own alias :slight_smile:

Agreed. But you are collecting a collection, which seems inane and
illogical to me. You are, however, absolutely right about the
fascinating flexibility of Ruby (though sometimes that thought is
coupled with deplorable distain of an old codger for excessive use :slight_smile:
Actually, I’m not an old codger, but I do come from old-school DB
design, so I guess that counts. I also come from a math background,
so “map” and “transform” were common terms, never the verb “collect”;
not even in engineering or physics. You don’t “collect” a group of
things. They are already collected. That would be sort of like using
the verb “gather”.

Todd


#19

On 5 Mar 2009, at 20:40, Todd B. wrote:

aware of, but silly nonetheless.
Collect makes much more sense to me than map as I’m collecting a bunch
of results and storing them in an array. But the great thing about
Ruby is that whichever makes sense, you can use it - and if neither
makes sense you can add your own alias :slight_smile:

Ellie

Eleanor McHugh
Games With Brains
http://slides.games-with-brains.net

raise ArgumentError unless @reality.responds_to? :reason


#20

Ken B. wrote:

arr2 = arr.collect{|ii| next 0 if ii == 3; ii+1}

Should be:

arr2 = arr.collect {|i| break if i == 3; i + 1 } # arr2 == nil

But, really, this does not seem the most sensible
way to implement anything except for illustrative
purposes.


Magic is insufficiently advanced technology.