Forum: Ruby deciphering poignant guide chapter 4 example

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
unknown (Guest)
on 2006-03-26 11:19
(Received via mailing list)
Hi,

I'm reading through the poignant guide, and am a bit stuck
at the end of chapter 4
( http://poignantguide.net/ruby/chapter-4.html ). It's
regarding the kitty_toys example:

#!/usr/bin/ruby

kitty_toys =
    [:shape => 'sock', :fabric => 'cashmere'] +
    [:shape => 'mouse', :fabric => 'calico'] +
    [:shape => 'eggroll', :fabric => 'chenille']

kitty_toys.sort_by { |toy| toy[:shape] }.each do |toy|
    puts "Blixy has a #{ toy[:shape] } made of #{ toy[:fabric] }"
end

and I've actually got three sticking points with it:

1. For one thing, I don't understand the point of the ":shape"
syntax. I don't understand why the author doesn't just write
the string "shape" instead. What exactly is a "Symbol" object
for?

The poinant guide says it's just "words that look just like
variables". And "Symbols are lightweight strings." But that
doesn't help me much. The PickAxe 2nd ed, in chapter 3 says,
"The construct :artist is an expression that returns a Symbol
object corresponding to artist.", so, I can understand that
(i.e. that there's some Symbol class and we're getting an
instance of it by using that notation), but I'm still not
getting the point... why not just use strings? What does
having "Symbols" buy the programmer?

2. Next up, why is

kitty_toys =
    [:shape => 'sock', :fabric => 'cashmere'] +
    [:shape => 'mouse', :fabric => 'calico'] +
    [:shape => 'eggroll', :fabric => 'chenille']

supposed to be shorthand for

kitty_toys = [
    {:shape => 'sock', :fabric => 'cashmere'},
    {:shape => 'mouse', :fabric => 'calico'},
    {:shape => 'eggroll', :fabric => 'chenille'}
]

How does that work? (Hmm... what does adding Arrays
in Ruby mean anyway? In Python it concatenates.)
Why does this shorthand exist? Hmm,.. it doesn't seem
to be saving much finger typing...

3. Finally, at the end of that example given in the poignant
guide:

#!/usr/bin/ruby

# ... Create kitty_toys as shown above, then

kitty_toys.sort_by { |toy| toy[:shape] }.each do |toy|
    puts "Blixy has a #{ toy[:shape] } made of #{ toy[:fabric] }"
end

how does that "kitty_toys.sort_by" line work? I believe that
braces and "do ... end" are equivalent, so it looks to me like
there's some kind of "loop-in-a-loop" going on, as in:

# Warning, Python code follows:
for i in range(1, 8):
    for j in range(1, 4):
        print i, j

Is there a more verbose way of writing that kitty_toys snippet
to make it a bit more obvious what's going on? I mean, I guess
the sort_by method is probably looking for something to sort
kitty_toys on, and we're telling it to use what it finds in
toy[:shape] for each hash it iterates over, but then, is that
next "each" looping over items in a given hash, or ... gah. I'm
not getting it. :)

Thanks,
---John
William J. (Guest)
on 2006-03-26 15:09
(Received via mailing list)
removed_email_address@domain.invalid wrote:
>     [:shape => 'sock', :fabric => 'cashmere'] +
> syntax. I don't understand why the author doesn't just write
> getting the point... why not just use strings? What does
>
>
>
> how does that "kitty_toys.sort_by" line work? I believe that
> braces and "do ... end" are equivalent, so it looks to me like
> there's some kind of "loop-in-a-loop" going on, as in:

No, kitty_toys.sort_by { |toy| toy[:shape] } simply produces an
array that's sorted by the shape of each toy.  Then he
iterates through the array and prints each toy.

> toy[:shape] for each hash it iterates over, but then, is that
> next "each" looping over items in a given hash, or ... gah. I'm
> not getting it. :)
>
> Thanks,
> ---John

Here's another way:

kitty_toys =
  [ { :shape, 'sock',     :fabric, 'cashmere'},
    { :shape, 'mouse',    :fabric, 'calico'},
    { :shape, 'eggroll',  :fabric, 'chenille'}
  ]

puts kitty_toys.sort_by { |toy| toy[:shape] }.map { |toy|
    "Blixy has a #{ toy[:shape] } made of #{ toy[:fabric] }"
}
Pierre Barbier de Reuille (Guest)
on 2006-03-26 16:26
(Received via mailing list)
Hi,

First, let me warn you that from what you describe, you have a Python
viewpoint on the data structures and that it is quite different from
many many languages.

removed_email_address@domain.invalid a écrit :
>  'chenille']
>  The poinant guide says it's just "words that look just like
>  variables". And "Symbols are lightweight strings." But that doesn't
>  help me much. The PickAxe 2nd ed, in chapter 3 says, "The construct
>  :artist is an expression that returns a Symbol object corresponding
>  to artist.", so, I can understand that (i.e. that there's some Symbol
>  class and we're getting an instance of it by using that notation),
>  but I'm still not getting the point... why not just use strings? What
>  does having "Symbols" buy the programmer?
You can see symbols as read-only strings used to very quickly find
informations in, for example, a hash table. The idea is to get an
efficient implementation while keeping the code readable by human
beings.

As you seem to come from the Python's world, you probably know that
Python's strings are immutable. And if you read a little bit about why
strings in Python are immutable, you will see it's because they wanted
to optimize the method lookup. Also you will learn that short Python's
string are coded differently so that a simple integer lookup is used for
method lookup. In the end, Python's string are what's called in Ruby
Symbol and Ruby's strings have no equivalent in Python, as Python has no
mutable string class.
>  'mouse', :fabric => 'calico'}, {:shape => 'eggroll', :fabric =>
>
>  Is there a more verbose way of writing that kitty_toys snippet to
>  make it a bit more obvious what's going on? I mean, I guess the
>  sort_by method is probably looking for something to sort kitty_toys
>  on, and we're telling it to use what it finds in toy[:shape] for each
>  hash it iterates over, but then, is that next "each" looping over
>  items in a given hash, or ... gah. I'm not getting it. :)
No, as said before, the sort_by returns an array ... which is further
processed, so it more subsequent loops.
You can expand it like that :

sorted_kitty = kitty_toys.sort_by { |toy| toy[:shape] }
sorted_kitty.each do |toy|
  [...]
end

The ruby equivalent of your python code would be :

(1...8).each { |i|  (1...4).each { |j| puts i,j} }
Simen (Guest)
on 2006-03-26 16:34
unknown wrote:

> The poinant guide says it's just "words that look just like
> variables". And "Symbols are lightweight strings." But that
> doesn't help me much. The PickAxe 2nd ed, in chapter 3 says,
> "The construct :artist is an expression that returns a Symbol
> object corresponding to artist.", so, I can understand that
> (i.e. that there's some Symbol class and we're getting an
> instance of it by using that notation), but I'm still not
> getting the point... why not just use strings? What does
> having "Symbols" buy the programmer?

Symbols are not merely lightweight strings, they represent names in the
Ruby interpreter. Check this sample out to see the difference:

  $ irb
  irb(main):001:0> "Hello".object_id
  => -605430718
  irb(main):002:0> "Hello".object_id
  => -605437098
  irb(main):003:0> :hello.object_id
  => 3985678
  irb(main):004:0> :hello.object_id
  => 3985678
  irb(main):005:0> :hello.object_id == :hello.object_id
  => true
  irb(main):006:0> "hello".object_id == "hello".object_id
  => false
  irb(main):007:0>

Symbols with the same values are the same objects, that is not the case
with strings. Symbols represents identifiers, strings values. For
example

  def method_missing( id, *args )
    puts id.class
  end
  asdf # undefined method

Would output "Symbol", since the method identifier is indeed an
identifier. Symbols are simply names of stuff. Use them when you need to
name something whose value may change, but not whose semantics (ie
ary[:greeting] may be "Hello world" or "Hello johnny" but it's allways a
greeting. You shouldn't use Symbols for stuff you're going to output to
some io, for example, there's no point in doing

  puts :"Hello world"

instead of

  puts "Hello world"

(Maybe some Ruby gurus could explain this better than me).
unknown (Guest)
on 2006-03-26 18:00
(Received via mailing list)
Hi --

On Sun, 26 Mar 2006, Simen wrote:

> identifier.
There's no constraint or direct cause-and-effect operating on Ruby
here, though.  For example, when you do:

   Array.instance_methods

you get a list of strings, corresponding to method names.  These
strings identify the methods, as much as :asdf does in your example.

>  puts "Hello world"
I'd add that a lot of Ruby methods take strings or symbols: attr and
friends, send, define_method, etc.  So there's also no point doing:

   send(meth_name.to_sym)

which one sees occasionally.


David

--
David A. Black (removed_email_address@domain.invalid)
Ruby Power and Light, LLC (http://www.rubypowerandlight.com)

"Ruby for Rails" chapters now available
from Manning Early Access Program! http://www.manning.com/books/black
unknown (Guest)
on 2006-03-27 02:28
(Received via mailing list)
> Here's another way:
>
> kitty_toys =
>   [ { :shape, 'sock',     :fabric, 'cashmere'},
>     { :shape, 'mouse',    :fabric, 'calico'},
>     { :shape, 'eggroll',  :fabric, 'chenille'}
>   ]

Ahh. Replacing the "fat commas" with commas. Thanks.
unknown (Guest)
on 2006-03-27 02:44
(Received via mailing list)
> First, let me warn you that from what you describe, you have
> a Python viewpoint on the data structures and that it is quite
> different from many many languages.

Well, hopefully you folks can help straighten me out then. :)

I've actually programmed in C, C++, Java, Perl, and Python,
but my trouble is that as soon as I learn the next language,
my brain tries to file previous language knowledge into
my grey matter's round filing cabinet. :)

> As you seem to come from the Python's world, you probably
> know that Python's strings are immutable. And if you read a
> little bit about why strings in Python are immutable, you will
> see it's because they wanted to optimize the method lookup.

I'll have to think about that. I don't see the connection between
strings and method lookup. In Python code, you write a name(),
and python works its way up the inheritance tree looking for
the function definition. You can't call a method like:

def my_func():
    print "hi"
foo = "my_func"
foo()  # Trying to call my_func, but it fails.

so I don't see the string/method-call connection you're
referring to...

> In the end, Python's string are what's called in Ruby Symbol
> and Ruby's strings have no equivalent in Python, as Python
> has no mutable string class.

Ah! Thanks.

> You can expand it like that :
>
> sorted_kitty = kitty_toys.sort_by { |toy| toy[:shape] }
> sorted_kitty.each do |toy|
>   [...]
> end

Got it.

> The ruby equivalent of your python code would be :
>
> (1...8).each { |i|  (1...4).each { |j| puts i,j} }

Ah. Sweet. Thanks for throwing that in. :)
Logan C. (Guest)
on 2006-03-27 03:04
(Received via mailing list)
On Mar 26, 2006, at 5:43 PM, removed_email_address@domain.invalid wrote:

> so I don't see the string/method-call connection you're
> referring to...

As a little aside, python lets you do:

def z(x):
	print x

a = z

a("Hello")
Hello

yes?

well ruby as you know, makes parens optional in method calls, what
this means is you can't directly assign methods around. So in ruby
you can do this:
send("z", "Hello")

or this
send(:z, "Hello")

In the first case, z has to be hashed to its numeric symbol code at
run time. This means somewhere inside the send method, its doing
"z".to_sym. When you use :z, the hash is computed at compile time,
and you're basically passing send a number, let's say 9.

send(9, "Hello")

In ruby Symbols don't have the extra level of indirection that
strings do. "z" is a pointer to the string object that contains the
character 'z'. :z is the number 9. (well not really, but it is *a*
number).

This is a reason for using symbols as hash keys, if you never display
the keys for instance. Its a number so equality comparsions become
trivial and hashing operations also become cheaper (I imagine so
anyway).
  Using symbols basically saves you one level of runtime lookup.
unknown (Guest)
on 2006-03-27 03:44
(Received via mailing list)
> Symbols are not merely lightweight strings, they represent
> names in the Ruby interpreter.

Ok, just like any other object:

foo = Foo.new  # An instance of class Foo.
bar = Bar.new  # An instance of class Bar.
baz = :famous_pizza  # An instance of class Symbol.

> Check this sample out to see the difference: [snip]
> Symbols with the same values are the same objects, that
> is not the case with strings.

Ah. Thanks.
Pierre Barbier de Reuille (Guest)
on 2006-03-27 12:50
(Received via mailing list)
removed_email_address@domain.invalid a écrit :
[...]
> the function definition. You can't call a method like:
>
> def my_func():
>     print "hi"
> foo = "my_func"
> foo()  # Trying to call my_func, but it fails.
>
> so I don't see the string/method-call connection you're
> referring to...
>
>
Ok, so, internally, when you write :

obj.fct()

the language first has to find out if and where "fct" is in "obj". To do
so, ruby will see "fct" as a Symbol and look for that symbol in "obj",
and Python will see "fct" as a string and look for it in "obj".
Unlike C++ and Java, the method resolution is done entirely at runtime,
so you have to use the *name* of the method to find it ! Remember that
any single object may or may not have the method defined, whatever its
class is !!!

If you prefer, these two statement are exactly equivalent :

obj.fct <=> obj.send(:fct)

The same equivalence in Python:

obj.fct <=>obj.getattr("fct")

Thus, in the dynamic languages, you need to keep a symbolic
representation of the methods, whether as a symbol or as a string
(symbol is more efficient, that's why Python's string are in fact
symbols ...).
[...]


Hope that helped !

Pierre
unknown (Guest)
on 2006-03-27 20:24
(Received via mailing list)
> well ruby as you know, makes parens optional in method
> calls, what this means is you can't directly assign
> methods around.

Ah! Hadn't thought of that!

Sounds like Symbols are used extensively inside Ruby. Almost
like some kind of smart pointer.

Will look up the docs on "send", and re-read the docs on Symbol.
Thanks!
unknown (Guest)
on 2006-03-27 20:29
(Received via mailing list)
> obj.fct()
>
> the language first has to find out if and where "fct" is in "obj".
> To do so, ruby will see "fct" as a Symbol and look for that
> symbol in "obj", and Python will see "fct" as a string and look
> for it in "obj".

Ah. Thank you for pointing that out! I see what you mean.
The runtime has to find the method *somehow*.

> If you prefer, these two statement are exactly equivalent :
>
> obj.fct <=> obj.send(:fct)
>
> The same equivalence in Python:
>
> obj.fct <=>obj.getattr("fct")

Ahh. Very informative. I'm gonna have to try and put some
of this up on the Ruby wiki.

> Hope that helped !
>
> Pierre

Very much so. Thanks again Pierre. :)
Eric Kidd (Guest)
on 2006-03-27 21:05
(Received via mailing list)
On Mar 26, 2006, at 5:43 PM, removed_email_address@domain.invalid wrote:
>> As you seem to come from the Python's world, you probably
>> know that Python's strings are immutable. And if you read a
>> little bit about why strings in Python are immutable, you will
>> see it's because they wanted to optimize the method lookup.
>
> I'll have to think about that. I don't see the connection between
> strings and method lookup.

Symbols and strings have various tradeoffs:

1) Comparing two strings may require looking at every character in
each string. This means some kind of loop, which takes time. On the
plus side, you can change the contents of a Ruby string.

2) Comparing two symbols takes only one instruction, because symbols
are stored as pointers to objects. Every time you write ":foo", you
get a pointer to the exact same ":foo" object. But since this object
is shared (and represents a constant), you're not allowed to change it.

The Ruby interpreter makes extensive use of symbols internally, for
performance reasons.

When should you use symbols? Mostly when you need some way to talk
about the (computer's) "names" for things, and compare those names
very efficiently. This generally occurs when implementing computer
languages, or when trying to parse natural languages.

How would you implement symbols? Usually with a hash table:

   # Pseudocode. This function would be called by the parser.
   def get_symbol_for_string(str)
     if hash contains object for str:
       return the object
     else
       make a new object, store it in the hash, and return it

Cheers,
Eric
William J. (Guest)
on 2006-03-28 01:14
(Received via mailing list)
William J. wrote:
> > kitty_toys =
> > 1. For one thing, I don't understand the point of the ":shape"
> > instance of it by using that notation), but I'm still not
> > supposed to be shorthand for
> > to be saving much finger typing...
> > end
> > # Warning, Python code follows:
> > not getting it. :)
>   ]
>
> puts kitty_toys.sort_by { |toy| toy[:shape] }.map { |toy|
>     "Blixy has a #{ toy[:shape] } made of #{ toy[:fabric] }"
> }

kitty_toys =
  { :shape, 'sock',     :fabric, 'cashmere'},
  { :shape, 'mouse',    :fabric, 'calico'},
  { :shape, 'eggroll',  :fabric, 'chenille'}
unknown (Guest)
on 2006-03-28 09:19
(Received via mailing list)
Ok, I added a wiki page:
http://www.rubygarden.org/ruby?UnderstandingSymbols

It could probably use some corrections, but please try not to tread to
heavily upon the delicately hand-crafted exposition. ;)

---John
This topic is locked and can not be replied to.