Robot Has No Heart

Xavier Shay blogs here

A robot that does not have a heart

Hash trumps case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Two equivalent functions
def rgb(color)
  case color
    when :red   then 'ff0000'
    when :green then '00ff00'
    when :blue  then '0000ff'
    else             '000000' # Default to black
  end
end

def rgb2(color)
  {
    :red   => 'ff0000',
    :green => '00ff00',
    :blue  => '0000ff'
  }[color] || '000000'
end

Even though these functions are equivalent, the second carries more semantic weight – it maps a symbol directly to a color. The case sample makes no such guarantees since you can execute any arbitrary code in the then block. In addition, a hash is easier to work with – you can easily iterate over the keys, extract to another method if you need reuse, or query it for other properties (for example, 3 colors are available). It is also easier to read – both aesthetically and because it contains fewer tokens. In almost all circumstances I will prefer a hash over a case statement.

Relationships in data are easier to comprehend and manipulate than relationships in code.

Hash#translate_keys_and_values

1
2
3
4
5
6
7
8
9
module CoreExtensions
  module Hash 
    def translate_keys_and_values(&block)
      inject({}) {|a, (key, value)| a.update(block.call(key) => block.call(value))}
    end
  end
end

Hash.send(:include, CoreExtensions::Hash)

It’s like symbolize_keys but a bit more flexible. It calls the block for every key and value in the hash. Of course you could tune it just do keys or values if you wanted. I do not want!

1
2
{"1" => "2"}.translate_keys_and_values(&:to_i)  # => {1 => 2}
{1 => 2}.translate_keys_and_values {|x| x + 1 } # => {2 => 3}

Facets patch

1
2
$ svn log svn://rubyforge.org/var/svn/facets/trunk -r 383 -v
---------------------------------------------------------------------

r383 | transami | 2007-11-03 23:31:54 +1100 (Sat, 03 Nov 2007) | 2 lines
Changed paths:
M /trunk/lib/core/facets/hash/op.rb
M /trunk/test/unit/hash/test_op.rb

Fixed bug in Hash#- Thanks to Xavier Shay.

1
2
3
4
--- ruby
require 'facets/hash/op'
{:a => 1, :b => 2, :c => 3} - [:a, :b]            # => {:c => 3}
{:a => 1, :b => 2, :c => 3} - {:a => 1, :b => 99} # => {:b => 2, :c => 3}

It may be small, but it’s authentic. In the 2.0.5 gem.

Enumerable#inject is my favourite method

Combines the elements of enum by applying the block to an accumulator value (memo) and each element in turn. At each step, memo is set to the value returned by the block. – RubyDoc

It just doesn’t sound very helpful. I must confess, it isn’t something I use everyday. But I love that when you do want to use it, it is oh so sweet. The canonical example is summing the elements in an array:

1
[1,2,3].inject(0) {|sum, n| sum + n} # => 6

Probably the most used pattern is converting an array to a hash:

1
[1,2,3].inject({}) {|a, v| a.update(v => v * 2)} # => {1 => 2, 2 => 4, 3 => 6}

Someone in IRC today wanted a nested send, something like @"string".send(“trim.downcase”)

1
"trim.downcase".split('.').inject("HELLO  ") {|obj, method| obj.send(method)} # => "hello"

What do you inject?

This is stupid: Hash#select vs reject

A little consistency would be nice…

1
2
{1=>1, 2=>2, 3=>3}.reject {|key, value| key != 1 } # => {1=>1}
{1=>1, 2=>2, 3=>3}.select {|key, value| key == 1 } # => [[1, 1]]
A pretty flower Another pretty flower