Skip to content

Latest commit

 

History

History
354 lines (220 loc) · 13 KB

pickaxe.rdoc

File metadata and controls

354 lines (220 loc) · 13 KB

Chapter 2: Ruby.new

Had never thought about this being a language feature:

Methods are invoked by sending a message to an object. The message contains the method’s name, along with any parameters 
the method may need. 33

But it’s pretty nice:

num = Math.abs(num)   # java. boo.
num = num.abs         # ruby. yay.

Didn’t know you could use while in one-lines

square = square*square while square < 1000

2.7 Blocks and Iterators

An excellent, succinct explanation of how yield works:

You can think of yield as being something like a method call that invokes the block associated with the call to the method 
containing the yield. 44

What does this mean?

You may be better off thinking of the block and the method as coroutines, which transfer control back and forth between 
themselves. 44 (footnote)

2.9 Command-line Arguments

Didn’t know about ARGF:

...the variable ARGF is a special kind of I/O object that acts like all the contents of all the files whose names are passed 
on the command line (or standard input if you don’t pass any filenames). 46

Chapter 3: Classes, Objects, and Variables

I always thought of a virtual attribute as an attribute which had no corresponding database column. Wrong. It’s simply an writer/reader which doesn’t rely directly on an instance variable (i.e. attribute). For example:

def price_in_cents=(cents) 
  @price = cents / 100.0
end

That’s a powerful abstraction that I had taken for granted (see Uniform Access Principle).

3.1 Attributes, Instance Variables, and Methods

Also, interesting is the blurry distinction between methods and attributes (55). Following the Uniform Access Principle there’s really no important, practical distinction between methods and attributes.

3.2 Classes Working with Other Classes

Didn’t know about this little beauty:

require_relative 'book_in_stock'

3.3 Access Control

Never thought of restricted methods this way. Always thought of them simply as internal methods (i.e. methods intended to be called only by other methods).

A good rule of thumb is never to expose methods that could leave an object in an invalid state.

Chapter 4: Containers, Blocks, and Iterators

Didn’t know that first and last could take arguments:

array = [ 1, 2, 3, 4, 5, 6, 7 ]
array.first(4)  # => [1, 2, 3, 4]
array.last(4)   # => [4, 5, 6, 7]

4.3 Blocks and Iterators

Didn’t know that you could explicitly define local variables by putting them after a semicolon in the block’s parameter list (73):

square = "some shape"
sum = 0 
[1, 2, 3, 4].each do |value; square|
  square = value * value
  sum	+= square
end
puts sum      # => 30
puts square   # => "some shape"

If inject is called with no parameter, it uses the first element of the collection as the initial value and starts the iteration with the second value. Meaning these two examples are logically the same (76):

[1,3,5,7].inject(0) {|sum, element| sum+element}    # => 16
[1,3,5,7].inject {|sum, element| sum+element}       # => 16

Too magical? You can also pass symbol to specify the method that you’d like called on each successive object in the array (76):

[1,3,5,7].inject(:+)    # => 16
[1,3,5,7].inject(:*)    # => 105

You can easily grant objects enumerable functionality:

Most of the internal iterator methods—the ones that normally yield successive values to a block—will also return an 
Enumerator object if called without a block (76).

This means, for example:

[1, "red", "orange"].each   # => create an Enumerator using an internal iterator

And now you can chain enumerable methods on to the returned object:

"cat".each_char.each_with_index { |item, index| result << [item, index] }

Enumerators Are Generators and Filters

Ruby 1.9 has an alternative syntax for lambdas:

lambda { |params| puts "params" }   # => 1.8
->params { puts "params" }          # => 1.9

Block Parameter Lists

As of Ruby 1.9, blocks have the same parameter list capabilities as methods (i.e. you can pass them splat args, default values, etc):

proc1 = lambda do |a, *b, &block| 
  puts "a = #{a.inspect}" 
  puts "b = #{b.inspect}" 
  block.call
end

Note that this is block also has closure. This means you can use lambdas to achieve lexical scoping, like in JavaScript. For example, say you had a build_invoice method.

class AccountsPayable

  def initialize(number)
    @number = number
  end

  def build_invoice
    puts "Invoice #{@number}"
    puts "Amount #{calculate_amount}"
    fetch_line_items.each do |item|
      puts "Purchased #{item[:quantity]} #{item[:description]} for #{item[:price]}"
    end
  end

  private

  def calculate_amount
    # ...complicated logic for taxes, surcharges, etc.
    19.48
  end

  def fetch_line_items
    # ...lots of code to fetch line items
    [ { description: "Apples", quantity: 7, price: 1.00 },
      { description: "Bananas", quantity: 12, price: 2.00 } ]
  end
end

Following the standard idiom, build_invoice is composed of several private methods – each responsible for a single, specific task. This is smart especially when the private methods are reused by other public methods (e.g. build_receipt method might also want to know about line items). By composing build_invoice of private methods, we’ve added a small about of indirection (i.e. we now must scan through three methods rather than one). But we’ve reduced complexity and presumably removed some duplication. Overall, a good trade.

But what about when these private methods aren’t reused? Or more importantly, what if you’re class has grown large and you have scores of private methods – each existing only to compose other methods. Tracking down the logic for a single method becomes increasingly tedious.

Assuming the private methods are not reused, we can use closure to compose a method without needing to create single-use private methods. For example, build_invoice could be refactored as:

class AccountsPayable

  def initialize(number)
    @number = number
  end

  def build_invoice
    calculate_amount = lambda {
      # ...complicated logic for taxes, surcharges, etc.
      19.48
    }.call

    fetch_line_items = lambda {
      # ...lots of code to fetch line items
      [
        { description: "Apples", quantity: 7, price: 1.00 },
        { description: "Bananas", quantity: 12, price: 2.00 }
      ]
    }.call

    puts "Invoice #{@number}"
    puts "Amount #{calculate_amount}"
    fetch_line_items.each do |item|
      puts "Purchased #{item[:quantity]} #{item[:description]} for #{item[:price]}"
    end
  end
end

Chapter 5: Sharing Functionality: Inheritance, Modules, and Mixins

Interesting. Authors don’t seem too hot on inheritance:

The use of the serve method shows a common idiom when using subclassing. A parent class assumes that it will be 
subclassed and calls a method that it expects its children to implement. This allows the parent to take on the brunt 
of the processing but to invoke what are effectively hook methods in subclasses to add application-level functionality. 
As we’ll see at the end of this chapter, just because this idiom is common doesn’t make it good design. (91)

Modules

Didn’t know :: was called the scope resolution operator. (93)

An academic distinction but interesting:

Ruby include does not simply copy the module’s instance methods into the class. Instead, it makes a reference from
the class to the included module. If multiple classes include that module, they’ll all point to the same thing. (94)

How method lookups work in Ruby:

Ruby looks first in the immediate class of an object, then in the mixins included into that class, and then in 
superclasses and their mixins. If a class has multiple modules mixed in, the last one included is searched first. (98)

So when should we use inheritance vs mixins? Of course it depends, making these pages are worth regularly re-reading. The basic synopsis, however:

The real world is built using composition, not strict hierarchies...And that’s where we need to move away from 
inheritance in our designs...Try to reserve inheritance for the times where it is justified. And try to explore 
all the cool ways that mixins let you write decoupled, flexible code. (99)gi

Chapter 6: Standard Types

Didn’t know that you can use underscore as delimiters to make large numbers easier to read:

123_456_789   # => same as 123456789

Didn’t know there’s built-in support for rational and imaginary numbers:

Rational(3, 4) * Rational(2, 3)	    # => (1/2)
Rational("3/4") * Rational("2/3")   # => (1/2)
Complex(1, 2) * Complex(3, 4)	      # => (-5+10i)
Complex("1+2i") * Complex("3+4i")   # => (-5+10i)

How Numbers Interact

What if you add two different types of numbers?

If the two numbers are different classes, the result will have the class of the more general one. If you mix integers 
and floats, the result will be a float; if you mix floats and complex numbers, the result will be complex.

Ranges

Didn’t know you could use ranges as conditions:

while line = gets 
  puts line if line =~ /start/ .. line =~ /end/
end

Chapter 7: Regular Expressions

Didn’t know that you can test to see whether a pattern does not match a string using !~ (113):

File.foreach("testfile").with_index do |line, index| 
  puts "#{index}: #{line}" if line !~ /on/
end

Never thought to sub! and gsub! as conditions:

Unlike sub and gsub, sub! and gsub! return the string only if the pattern was matched. If no match for the pattern is 
found in the string, they return nil instead. This means it can make sense (depending on your need) to use the ! forms 
in conditions. (114)

7.3 Digging Deeper

Different ways to create regular expressions:

/mm\/dd/	            # => /mm\/dd/
Regexp.new("mm/dd")   # => /mm\/dd/
%r{mm/dd}	            # => /mm\/dd/

I did know about extend mode but probably don’t use it enough:

Complex regular expressions can be difficult to read. The x option allows you to insert spaces and newlines in the 
pattern to make it more readable. You can also use # to introduce comments. (115)

Whoa! Didn’t know you could use pre_match and post_match to avoid using ugly pearl-like globals (116):

def show_regexp(string, pattern) 
  match = pattern.match(string) 
  "#{match.pre_match}->#{match[0]}<-#{match.post_match}"
end
show_regexp('very interesting', /t/) # => very in->t<-eresting

Character Classes

You can create the intersection of character classes using && (118):

# matchs all lowercase ASCII letters that aren’t vowels
"now is the time".gsub(/[a-z&&[^aeiou]]/, '*')    # => "*o* i* **e *i*e"

Don’t forget that you can use part of the current match later in match using the \1 notation. For example, this matches duplicated substrings (120):

show_regexp('Mississippi', /(\w+)\1/)	# => M->ississ<-ippi

Didn’t know that both sub and gsub can take a block:

"quick brown fox".gsub(/[aeiou]/) { |vowel| vowel.upcase } # => "qUIck brOwn fOx"

This is awesome too: “You can also give sub and gsub a hash as the replacement parameter, in which case they will look up matched groups and use the corresponding values as replacement text” (122):

replacement = { "cat" => "feline", "dog" => "canine" } 
replacement.default = "unknown"
"cat and dog".gsub(/\w+/, replacement)  # => "feline unknown canine"

Backslash Sequences in the Substitution

You can also use backslash sequences as the second argument of sub and gsub:

"fred:smith".sub(/(\w+):(\w+)/, '\2, \1')    # => smith, fred

7.4 Advanced Regular Expressions

Awesome. How to add comments within regular expressions:

The sequence (?# comment) inserts a comment into the pattern. The content is ignored during pattern matching. As 
we’ll see, commenting complex regular expressions can be as helpful as commenting complex code. (123)

Didn’t know about zero-width positive lookahead or zero-width positive lookbehind matchers. Pretty cool. (124)

Also, fairly advanced discussion of backtracing. (125)

Note: Moved pretty quickly through the advanced section would be good re-read in a few months to help everything sink in better.

Chapter 8: More About Methods

Didn’t know a default argument could reference previous arguments (132):

def surround(word, pad_width=word.length/2) 
  "[" * pad_width + word + "]" * pad_width
end

If you’re using a splat operator only to pass arguments on to super, you can omit the argument name. So these two snippets are the same:

class Child < Parent
  def do_something(*not_used)
    # our processing
    super
  end
end

class Child < Parent 
  def do_something(*)
    # our processing
    super
  end
end