The Memory Palace of Effective Ruby
In my SimpleThread article, Total Recall: Memorize a Tech Book,
I introduce the strategy of memorization known as The Method of Loci. In that article I explain how I memorized Effective Ruby: 48 Specific Ways to Write Better Ruby[b] (we highly recommend picking up a copy of at www.effectiveruby.com). This post provides a text-based version of my memory palace for Effective Ruby.
Note that the memory palace I visualize for the Effective Ruby memory pegs is the offices from a past job I had as a Systems Programmer in San Antonio, Texas. Your memory palace should be a place for which you have clear memories.
Also, as I explained in my post, a memory palace works best when the ideas and situations are extreme and a little weird, so please keep that in mind as you peruse.
Chapter 1: Accustoming Yourself to Ruby
1: Charles Manson
I walk into an atrium of my memory palace. It was full of people. Three stand out: Charles Manson, Neil Armstrong, and Zero the superhero (who stands for truth, justice, and the Ruby way.) Zero, the hero, is wearing a caped outfit with a logo of a big zero. Zero has his right hand high in the air in the symbol of truth. Everyone else -- but for Charles and Neil -- also has their right hand high. Armstrong is decked out in full space suit. He is the symbol of space, which one might consider as nothingness, or emptiness. I don’t know about you, but I pronounce Neil with a silent ‘e’ or Nil. Charles Manson? Well, he’s a false profit.
In Ruby, everything but false and nil, are considered true. Even the numerical value of zero is considered true. This is the Ruby truth. Remember that everything in Ruby is an object, so, if you need to compare against false, put false as the left operand.
2: Room of Astronauts
I continue my journey by walking into the first room. It’s full of astronauts. Each of them have their visors closed. I think: They can’t all be Neals [phonetically Nil.] Oddly, each of the astronauts has a string hanging around his or her neck. I pull the string on the first. Her visor opens revealing a girl with the name Alice magic-markered across her forehead. Obviously not Neal. I pull the string on the next astronaut. The visor opens and hovering in the middle of the helmet is the integral number 17. Definitely not nil. I do the same on the third suit and its raising visor reveals an assortment of fruit. Again, not nil. I go to the fourth and, tiring of pulling strings, I just ask him (or her, or whatever the hell it might be) “Nil?” And the space suit raises it’s right hand in the symbol of truth before collapsing into the void. Hey, found a Neal! I ask the same of another suit and it shakes its helmet side-to-side to indicate no. Tiring of all this nonsense looking for Neals, I spread my hands wide and compact the group of astronauts into the corner. As I do that, many of the suits collapse into nothingness -- they must have been Neals as well.
Any Ruby object can be nil. If you want to know, just ask, with the method nil?. But, if your code needs a string, integer, or array, you can also use to_s, to_i, or to_a to convert the object to the expected type. It seems redundant to convert what you expect to be a string with to_s but, if it is nil, the to_s returns an empty string; nil.to_i returns zero; and nil.to_a returns an empty array. And remember that Array#compact removes all instances that are nil.
3: Crypt Keeper Wearing Pearls
As I walk into the second room, I hear the raunchy cackling laughter of an aging tiny man. There’s a coffin leaning against the far wall inside is none other than the Crypt Keeper -- wearing strings of pearls. Aghast, and not watching my step, I stumble into a wheelbarrow. It’s full of chipped rubber tires of the type you use for paths in a park. Oddly, there’s a crisp dollar bill on top of the load destined for some path. Standing next to the Crypt Keeper, a man is holding a match in one hand while incessantly pointing at it with the slowly curling index finger of his other hand. He’s intensely looking at me as if he’s trying to tell me something. The last thing strangeness of the room is that there’s an English dictionary sitting on the middle of the table.
Peter calls this item: Cryptic Perlisms. The Crypt Keeper, as do I, likes Perl. Ruby was influenced by Perl but you should avoid using the Perl-like syntax supported by Ruby. Probably the biggest Perl confusity is the use of equal tilde (=~) followed by a regular expression. As the odd man was trying to tell us with his tilde shaped index finger pointing to a match, use String.match instead. Then, instead of the obscure dollar sign numerical pattern (e.g. $1) you use the array returned from String.match (e.g. my_match[1]). The wheelbarrow load of path filler symbolized the $LOAD_PATH which is much less cryptic that the Perlish dollar-colon ($:). Know that, if you require(‘English’), you’ll have at your disposal a dozen or so explicitly named, in plain English no less, alternatives to Perl codes like $! and $_ (http://ruby-doc.org/stdlib-2.0.0/libdoc/English/rdoc/English.html).
4: Duct-Taped Lady
When I left the Crypt Keeper’s lair, across the hall I saw a lady standing in an open area with duct tape across her mouth. It recognized her! It was Constance, I went to highschool with her. She had been the type of person you admire; one you might compare yourself against. But now…. She had mutated. She had gained a lot of weight and her hair was multiple colors and she looked a bit mean and not to be trusted. I spied a fire extinguisher against the wall. It was one of those types that spews a freezing spray. Quickly, I grab it and go back in time. Yeah. That’s right. I can go back in time. I’m a Ruby programmer for gosh sakes -- we can do anything. Constance is back to her comparable glory and I freeze her with the fire hose. I see to my right a table with three or four Constance-like characters. I freeze each one of them individually before spraying the whole group. I leave the open work area and, in my excited frenzy, to be certain that neither Constance nor her compatriots were never be changed again, I sprayed the whole damn module.
The duct tape, rather than suggesting that Constance had been muted, symbolized that she had mutated. Yeah, it’s pushing metaphors a bit but it’s hard not to remember the visualization of a chunky lady standing with duct tape across her mouth. To prevent Ruby constants from changing, zap them with the freeze method. If they are in an array, zap each element (maybe with the map method) and then the entire array. Better yet, put constants in a module and freeze the module.
5: Flashing yellow light
I was cooling down from my frenzied freezing fury as I moved down the hallway towards the next room. An irritating noise that reminded me of the Star Trek engineering warning sound (https://www.youtube.com/watch?v=5g6ngBMwDnI) permeated the corridor. The room had a yellow flashing light high on the wall. Just below the twirling light was button labeled with the letter ‘w’ . Lower yet was a VERBOSE laminated sign that, oddly, had a crisp dollar bill taped to it. I didn’t bother to read it as I really didn’t feel like I had the time. To put an end to the bothersome light and annoying noise, I pushed the warning button to the off position.
Peter Jones tells us to not to do what I just did and ignore compile and runtime warnings during development and test. You turn on warnings by passing a -w to the Ruby command or stuffing it in the RUBYOPT environment variable. If you have a specific need to disable warnings, set the $VERBOSE global variable to nil.
Chapter 2: Classes, Objects, and Modules
6: Grampa
Back in a now quiet corridor I move into another room. I saw my Dad sitting in front of a small desk that had a phone on it. My Grandpa was sitting in front of another desk phone. Between my ancestors was a desk with a ghost manning its phone. On the other side of the room was a few more staffed phone desks with a Missing Persons sign on the wall behind them.
Ruby looks for methods up the inheritance stack. Understand that including modules and creating singleton class and object methods creates ghosts in your object’s inheritance tree. Ruby goes up the stack trying to call methods on each class and object instance in the tree until it finds a method implementation. If Ruby can’t find the method, it goes back up the stack but, this time calling method_missing.
7: Superman With/Without Parents
When I walked into the subsequent room, standing there was none other than Superman, with his parents behind him. Wait. No, his parents weren’t there. Yeah. Yes they were. No… They’re gone again. Anyway, I think it would be better if the parents were there.
When you use the keyword super (to invoke the method you are overriding) the presence of parenthesis changes behaviour. You can call super anywhere in your method (whereas with Java and C++ it had to be as the first statement in the method.) You can call super passing constants or variables local to your method or just pass along the parameters of the enclosing method. It’s up to you -- unless you don’t use parentheses and don’t pass any arguments to super. In that case Ruby automatically passes the arguments of the enclosing method. If you want to invoke super with no parameters, specify empty parens. Actually, in general, be more liberal with your use of parentheses. Moving to Ruby from Java, I initially liked the terseness and freedom not to use parenthesis but I keep getting advice from experts like Peter Jones to use them.
class JustMe < MyDad
def with_help_from_dad (a, b, c)
super(a, b, c)
super a, b, c
super(1, 2, 3)
super # passes all args from enclosing method
super() # force no args
end
end
8: Sub Shop
The next room was large and each of the four corners had something intriguing going on. [Your memory palace might use separate rooms.] In the first corner was a Super Subs kiosk. I was hungry so I looked over the menu. It as a confusing hierarchy of sub options and ingredients. At the top was classes of three meats: Roast Beef, Ham, and Turkey. Next in the tree was cheeses: Sharp Cheddar, American, and Provolone. At the bottom was the class of condiments: Lettuce, Pickles, and Onions. I order a Roast Beef, Sharp Cheddar, and Lettuce. Thinking I might want a second sub for the ride home, I asked them to copy that order. When I sat down and hungrily open my sub I saw I only got a Lettuce sub. The stupid sub builder created a Lettuce sub and neglected to add the attributes of the sub’s hierarchical parent’s Sharp Cheddar, and Roast Beef. And, yeah, my copy request was not initialized appropriately either.
Remember to invoke super when initializing subclasses. Understand that, when invoking new, only first version of initialize is invoked so remember to use super to initialize the parent. And remember to use super when you define initialize_copy.
9: Football Receiver
I threw my lettuce sub in the trash and glanced over to the next section. There was a hologram running where I was a football receiver. In the video sequence, I yell, selfishly, to the quarterback requesting access to the ball. He returns it to me and I’m able to successfully access it. The hologram continues with another user story but this time there’s an anonymous receiver. He must be some local guy that walked on the field. He selflessly waves his hands showing he’s open and requests access to the ball. But the quarterback never throws it to him.
Peter uses the word “vexing” to describe Ruby’s parsing oddities with setter methods -- whether they are manually created or generated with attr_writter or attr_accessor, setter methods can only be called with self as the explicit receiver (as I did selfishly above.) Without a receiver (such as the selfless anonymous receiver above) Ruby creates a local variable. But don’t go too crazy with self, you don't need it when calling non-setter methods.
10: Hash, Again!
Moving on to my next stop I saw none other than my childhood family sitting down to, yet another, dinner of hash. Hash is so easy to make and it was my mother’s go-to meal for combining left-overs. But I always wanted a meal with a bit more substance and structure.
In our jobs we use arrays and hashes all the time. I would venture to say that relational databases and NOSQL are simply complex extensions to hashes and arrays. Ruby hashes are great but sometimes we use them to store things that might work better in a class -- yet we don’t need the extra baggage of a class beyond the local code context. That’s where Struct.new should be used. Two things to remember about Struct.new:
- You can pass a block to define methods for the instantiate of the Struct.
- If you assign the return value to a constant, it will function like a class.
The following shows a contrived example that creates a Struct pseudo-class for use in a Set (the use of which is explained in item #18 of my memory palace):
Person = Struct.new(:first, :last) do
def eql?(other) first == other.first && last == other.last; end
def hash; "#{first}#{last}".hash; end
end
11: Cabinets
I left my less-than-satisfying meal of hash and came upon three filing cabinets. Each had nameplates: invoicing, sales, and inventory.
Peter tells us to namespace your classes with modules and to put them in directories that match their namespace. Coming from Java, I was a bit confused where to place modules or if I should even use them. In Java, the namespaces (packages) must match the directories they are contained it. Ruby doesn’t impose such restrictions but it is, nevertheless, a good practise. Note, should you create a class that matches a name of a standard Ruby class or a class from another gem, simply prefix the class name with double colons: e.g.: ::Queue.
12: Ice cream
Cabinets are a bit boring but next up was an ice cream kiosk with a sign that presented a selection of four flavors :
1. High Point of Your Day (equal?)
2. High Point Unless hAsh (eql?)
3. Best Value (==)
4. Just In-Case (===)
I order a number two -- mostly because I wondering about why it was the ‘A’ in the word hash that was uppercased. But the ice-creamista said: "Weren’t you just eating Hash with your family?". “Yeah,” I respond. He says “Well... then you need to give us the recipe for eql? as High Point UA doesn't sit well with Hash."
Then it all made sense to me. The recipe for equal? checks pointers to objects and you should have no reason to change its implementation. The eql? method, on the other hand, defaults to equal?. it often requires an new recipe/implementation. Hash, for example, uses eql? for key collision. The recipe for Best Value (==) is often usable as it checks against object values. Nonetheless, you may need to reimplemented == to compare against appropriate values in your class. The triple equal (===), by the way, is used by case statements. The left operand is given to when and the right is given to case. Peter suggests we follow the guidance in number 13.
13: Space Ship
Not knowing how to provide a recipe for my order of High Point UA ice cream, I follow Peter’s advice and move on to Item 13. I was awestruck to see a virtually incomparable Spaceship hovering mid air. Let me see if I can do it justice with a description: Picture a less-than symbol, a greater than symbol, and an equal sign between them. That’s right: the spaceship operator. OK, so maybe it is moreso comparable than incomparable.
Peter tells us that implementing the spaceship operator (although he doesn’t use that term) is trivial: Include comparable in your class and, in the spaceship method, return nil, if the object types cannot be compared. Otherwise, return -1, 0, or 1 respectively based on if the comparison is less than, equal to, or greater than. Peter also points out: If instances of your class are to be used as hash keys, alias eql? to == and create a sensible hash implementation:
alias_method(:eql?, :==)
def hash; ‘sensible’.hash; end
14: Shotgun
Next spot had a family, the members of which were holding a variety of weapons. Surrounding the family was a myriad of cool stuff: videogames, bags of money, motorcycles.... I was looking, perhaps too closely, at a motorized skateboard and one member of the family, a man holding a shotgun, members decried: “Protect your stuff but share with family.”
Coming from Java (and C++ before that) I made heavy use of the protected and private keywords. But most of my Ruby experiences showed little use of the private keyword much less protected. So I was happy to hear Peter tell us:
- “Share private state through protected methods.”
- “Protected methods can only be called with an explicit receiver from objects of the same class or when they inherited the protected methods from a common superclass.”
15: @@: Two spiral staircases
Backing away from the overly intense shotgun bearing man, I bump into something hard and metal. Turning around I see a pair of spiral staircases. The Italian word for spiral staircase is chiocciola and it also is the word for snail and the at/@ symbol. One of the two staircases had one of those yellow triangle warning signs with a big exclamation point on it baring entry (https://emojipedia-us.s3.amazonaws.com/thumbs/240/google/119/warning-sign_26a0.png)
I can summarize Peter’s warning with simply: “At ought to be used once in classy code” but let me explain in a bit more detail: Variables defined with a double-@ are class variables but their use cause issues with inheritance. The solution is to use class instance variables -- defined with a single @ in a class method. If you are concerned at all about multiple instances across threads, Peter tells us to consider including the singleton module.
Chapter 3: Collections
16: Mice
Back In the hallway I see a mouse scurrying out of a door. Behind him a man in a white lab coat runs up and gently snatches the mouse into his cupped hands, smiles, and whispers sweetly to the rodent. I follow the two back into the room where there’s another lab technician. He was wearing a lab coat that was similar but for one item: this guy’s sported a marshal’s badge. The Marshal was madly performing tests… on mice. The first scientist would gently hand a dozen or so of his beloved mice to the second technician who would, before running tests, clone the mice and hand the originals back to the rat-lover. Which as a good thing because, during those tests, those cloned mice would be mutated in indescribable and unexpected ways.
I watched a series of tests being performed and, as it turns out, the mad scientist would, most often, duplicate the mice rather than clone them. Sometimes he’d use his Marshall privileges and perform deep copies on the mice before running tests.
Everything in Ruby is an object. As such, arguments are passed to methods by reference (but for Fixnum.) If you suspect a method might mutate an argument, dup or clone them into local copies. The dup method always creates a mutable object but it won’t pull singleton methods. On the other hand, clone pulls singleton methods and honors freeze operations that may have been performed on the passed argument. If the shallow copies done by dup and clone will not suffice, use Marshal.
17: Ray's Pizza
Just next to the mice lab was a Ray’s Pizza kiosk. If you’ve ever been to New York City you know there are a couple of different Ray’s pizza companies all saying they are the real Ray’s. This kiosk was run by a very special Ray: Colonel Albert Ray. The Colonel has an odd quirk: you must enter his restaurant as a group. His quirk was known as the Colonel A. Ray method. A rule that seemed odd because, in the sitting area I saw a waiter trying to take an order from an empty table. I said to Ray: “But it’s just me.” Colonel A. Ray turned to a waiter and said “Party of one.” As my group of one was being seated I saw whole section of tables each with one person seated with a key hanging from their necks. “Not very efficient,” I thought.
Peter Jones tells us to use the kernel’s Array method to convert nils and scalars to arrays to make it easy for folk to use a method that otherwise would only handle an array. But be aware that, if you pass a Hash to the Array method, it will be converted to an array of arrays each containing two entries: one for the key and one for the value.
def initialize(array_nil_integer_string_etc)
Array(array_nil_integer_string_etc).each {...}
18: Doorman holding a clipboard
After finishing my slice of pizza I move on to the next room. I’m stopped as a beefy man thrusts a clipboard in my face and says “Name?” I tell him my name and he says that there's already a Denoncourt in there so I could not enter. I grab his clipboard and look at the list implementation. He was using an array to manage inclusion! Peter Jones tells us to use Set for inclusion testing. Looking items up in an array has a big-O rating of O(n), which is bad, whereas a Hash has O(log n), which is good. But converting your code to use a Hash is problematic and a Set behaves like an array but is implemented internally with a Hash. Objects inserted into a Set must also be usable as hash keys so you need to implement eql? and hash . Peter suggests you could to that in a Struct.new block (see item #10.) So I scribbled the followed code onto the doorman’s clipboard:
require('set')
class AccessList
Person = Struct.new(:first, :last) do
def eql?(other) first == other.first && last == other.last; end
def hash; "#{first}#{last}".hash; end
end
def initialize
@on_list = Set.new
end
def add(first, last)
@on_list << Person.new(first, last)
end
def on_list?(first, last)
@on_list.include?(Person.new(first, last))
end
end
19: Reduce Taxes with J&T Block
My brother pops out of the room, sees me, and claims he’s just had his taxes done. I walk into the room and there’s an J&T Block sign with the tagline of “Reduce Taxes with J&T Block” They had a machine on a block. It required a list of income and the tax amount you’ve already paid. The machine on the block shakes for awhile and finally spits out a ticket with the taxes you owe.
Peter Jones, as am I, is pretty infatuated with Ruby’s reduce method. It is one of the Ruby methods that directly supports functional programming. Peter tells us reduce takes 3 things:
- An enumerable object
- A starting value
- A block to take the starting value, spin through the enumerable, and spit out an accumulator
In the J&T image 1) the enumerable object, is the list of income; 2) the starting value, is the tax amount paid; and 3) the code block, is the shaking machine that’s sitting on the block.
Peter advises that the block should always specify a starting value for accumulator and also it should always return an accumulator.
Note: Despite J&T Block’s claims Collection#reduce doesn’t really reduce your taxes (it just spins through them) and arguably (in functional programming’s philosophy) it shouldn’t mutate anything anyway.
20: Cash Machines
I didn’t go for the “reduce your taxes” thing. I’ll do my own taxes, thank you very much. Just beyond the J&T Block kiosk I saw four cash machines. Having spent too much for ice cream and pizza, I needed some cash. I went to the first machine, keyed my ID and was instantly upset when the monitor told me I had $0 in my account. That couldn’t be right. I tried the second cash machine and this one told me I had $42 left. That’s weird. For one thing, as a coder, 42 is a conspicuous number. I tried the third and its monitor read: “Our Hash-based system does not has_ your key?” The English was a bit off but I thought, just maybe, my memory had one of the 64 alphanumeric characters wrong. I tried the fourth and it said: “Sorry but we were unable to fetch your key.”
I did what any conscientious programmer would do and hacked into the banking system and looked at the code. All four machines used a Hash to store accounts. As you know, Hash allows you to specify a default value to be used if a key is not found -- otherwise it returns nil. The first machine’s code specified
account_hash = Hash.new(0)
And when asked:
account_hash[alphanumeric_key]
It returned a zero. The second machine’s code specified 42 as a stupid default value. The third machine’s code used:
account_hash.has_key?(alphanumeric_key)
and correctly discovered that the key, in fact, did not exist. The fourth, more concisely, used:
account_hash.fetch(alphanumeric_key)
Peter tells us to consider consider using a default Hash value, to make use of has_key?, fetch, and to not assume a Hash will always return nil when passed a nonexistent key. He recommends that you not use default values if you pass the hash to code that assumes invalid keys. He also suggests passing a block to Hash.new, which would then executed when a key is not found. That block can take two forms: one with no arguments and one with two arguments of the hash itself and the the requested key:
hash = Hash.new { ‘default value’ }
hash = Hash.new { |hash, key| hash[key] = “default value for #{key}” }
21: Bill Collector
As I left the room, Bill came up and said he was acting as collections for the office coffee fund. And me with no cash! I asked Bill “Why are you collecting? Isn’t it Ann’s job? Did you inherited the job?” Bill said: “No, Ann delegated to me, but she’s still responsible.” After I paid him -- with a check, as I still had no cash -- he asked me to initialize a copy of the receipt. The paper oddly felt freezing and had a yellowish taint.
Peter asks us to prefer delegation to inheriting from collection classes. Don’t extend Hash or Array. Rather extend Forwardable, include Enumerable, define a def_delegator to forward method calls to the delegated collection object. He also says you should write a initialize_copy method that duplicates the delegation target and also implement freeze, taint, and untaint methods that do their business with the delegation target before invoking super.
Chapter 4: Exceptions
22: Poop
Moving along the hallway I unexpectedly step on a pile of poop. It was infused with strings. As I cursed and attempted, ineffectively, to handle the poop problem, a man with a long scraggly beard that looked like Forrest Gump runs up, stops and says: “Shit Happens, No Runtime now, Just handle it the standard base way.”
Peter tells is to avoid raising strings as exceptions because they’re converted into RuntimeError objects, which should only be used when you are going to terminate the application. Create a custom exception instead, one that extends StandardError . Better yet, create a base custom class for your application. Use “Error” as a suffix (or I might also suggest “Poop”.) Be sure your initialize method calls super passing an error message. Peter also says that, if initialize sets the message attribute, know that setting error message with raise takes precedence over the one in initialize ,
23: Titanic
As I pondered the Forrest Gump doppelgänger’s advice, I opened the door to the next room of my memory palace. I was hit with a blast of arctic air, the smell of salt, and the sounds of screaming people as I saw the horror of the Titanic sinking in the northern Atlantic ocean. Good for me, I thought, as I stuck my poop infested sneaker into the Atlantic for an improvised cleaning. Then I noticed big pile of life preservers beside me on the entryway. I could rescue these people! So I whipped a life preserver into the middle of a group of drowning people. They all jumped on it and perished. Then I realized: I should only rescue those that I was able to handle and I needed to be selective about who I rescued. I thought: Save over-50 Ruby programmers, good-looking women, and, I guess, children. I start throwing life preservers to individuals and rescuing them. When I was down to the last life preserver I tried to rescue a big sailor. I pull him to the side and I realized, I didn’t have the strength to pull him up -- I wasn’t able to handle his rescue! He reaches into his breast pocket, pulls out a letter, hands it to me and pleads me to mail it to his wife. The thought of being responsible for that task irritated me but I took the letter anyway as I kicked him back into the abyss. Perhaps someone else will be able to handle his rescue.
I learned a lot from my Titanic rescue (and Peter’s book.) I learned that you should only rescue specific exceptions and only those exceptions and that you can handle. I learned that you should rescue the most specific exceptions first. The higher a class is in the exception hierarchy the lower it should be in your chain of rescue clauses. From my failed attempt at rescuing unidentified people, I learned to avoid rescuing generic exceptions such as StandardError. Peter suggests that sometimes using an ensure clause instead of rescue might be a better solution.
Now let me explain why I was irritated with the sailor’s request to mail that letter: Sometimes you rescue an error, email it or otherwise forward the issue to an external service, and then re-raise the exception. I was irritated because I didn’t trust the mail service. Just in-case shit happens during handling of an error you plan on re-raising, Peter suggests that you consider a utility method to which you pass the original error, send the notification, then, in a rescue clause, re-raise the original error:
def notify_staff (original_error)
# email
rescue
raise(original_error)
end
24: Yield
Feeling pretty damn good about saving all those over-50 Ruby programmers, attractive women, and a kid or two, I moved on to the next room in my memory palace. It had a yield sign on its door with a concierge in front of it. The concierge said: “I’m Gaston and I’ll be ensuring resource availability while you stay with us.” He then unlocks the door, yields my entry, and states: “I’ll ensure the lock is restored when you leave.” I then take a big step up onto a cement block as I enter the room.
Peter implores us to use what he calls the “block and ensure” pattern where you use a class method implementation that abstracts resource management. In that class method you write an ensure clause to manage resources. Peter also warns us to initialize variables before they are used in your ensure clause. Effective Ruby has a great example of the block and ensure pattern but below is my example that follows the concierge narrative. In my class, a room should only be unlocked when some enters. It should then be locked when the party finishes doing stuff in the room. If you call Room.unlock, you get a room that is already unlocked, the unlocked method will perform your given block and then, magically, relock the room. If, however, you don’t pass a block the the class unlock method, or new an object, you’ll need to manage resources yourself by unlocking then locking.
class Room
def initialize
@locked = true
end
def self.unlock
room = new
room.unlock
if block_given?
yield(room)
else
room
end
ensure
if block_given?
room.lock if room
end
end
def unlock
@locked = false
end
def lock
@locked = true
end
def do_stuff
raise(StandardError, "room is locked") if @locked
puts "doing stuff in unlocked room"
end
end
Room.unlock do |room|
room.do_stuff
end
# without a block, you'll need to lock it back up yourself
room = Room.unlock
room.do_stuff
room.lock
# or, if you new the object, unlock and lock
room = Room.new
room.unlock
room.do_stuff
room.lock
25: The Aflac Duck
As I walk into the room I see a duck, you know, that Aflac Ensureance duck. The duck quacks to me sinisterly: “You never return well from use of ensureance.” He continues: “And no throwing stones while you are at it.” I did remember throwing stones at ducks as a kid, I wonder how he knew about that. Then he adds: “Next break and I’m out of here for bad. I mean... I’m out of here for good.”
The duck is right, you should not return from within an ensure clause. Peter says that suggests you have something wrong with your logic. Also, don’t throw anything at ducks or in an ensure clause. Peter also says that when iterating, don’t use next or break in an ensure clause. Peter finishes by telling is to, in general, don’t alter flow in an ensure clause -- do that in a rescue clause.
By the way, advertising’s use of the Aflac duck, Tony the Tiger, Jolly Green giant, and Ronald McDonald are all examples of usage of the same concept of the memory palace. If advertisers can get a visual in your head that associates their product with that image, there’ll be a better chance you’ll buy their product.
26: Basketball
At my next stop I saw a bunch of guys, all wearing rescue team T-shirts, practicing basketball rebounds. A player would begin by taking a shot from a distance that was sure to miss. The other players would try to rescue the attempt by rebounding the ball. The coach would made a log entry for each rebound retry but, after three failed retries, he would raise his hand and blow his whistle to signal the drill was over.
Sometimes you want to retry a request to, let’s say, an external resource, that, every now and again, it fails. But on a second try, you are pretty sure it will work. Peter tells use to never use retry unconditionally. He suggest that you create a counter variable outside the begin block and then re-raise the exception until you hit an upper bound of retries. He also suggests that, like the coach above does, each retry should be logged. And you ought to increase the sleep time on each successive retry.
27: Playing Catch
As I left the room, I saw three kids playing throw and catch with a ball. Two were standing on blocks and passing the ball between them. They’d write something on the ball, with a felt tip magic marker, and then throw it. The other kid, that was also standing on a block, would catch the ball, read the note, nod in acknowledgement of receiving the note, make her own note, and throw the ball. The third kid watched carefully but he never raised his hands in attempt to get an opportunity to handle the ball. I asked him why he doesn’t try to get the ball and he said: “Oh, its a real exception when I get to handle it. I’m here incase things get out of control -- like if the ball is going towards a window or the street. Then I’ll raise my hands and rescue the ball.”
[By the way, the human hand is able to catch a ball easily because it has 27 bones, which is also the number of this Effective Ruby item and a way for me to remember what number I’m at.]
Coming from Java, where you throw and catch exceptions, this throw/catch and raise/rescue dichotomy always confused me, I just remembered to not use throw/catch. But Peter tells us that while throw/catch is not a way to handle real exceptions, it is prefered over raise/rescue when you want to pass back information from a complicated control flow. The cool thing about catch is that, if it has an associated variable assignment, you can send an object up the stack. Peter does warn us that the blocks that contain the throw/catch be as simple as possible.
Chapter 5: Metaprogramming
28: Wall Phone
Inside the next room I hear the phone ringing, I run in and take it off the hook. The person on the line says: “This is Ruby Singleton. You registered for a callback when someone was extending their memory by reading your blog post or including your comments in their notes." Well, that’s almost too meta for me. “Thanks Ruby” I said “I’ll handle it.”
Metaprogramming can be both scary and exhilarating. You stay away from it until you try it then it becomes all too easy to be caught up in its use. Ruby hooks are a relatively straightforward way to provide more maintainable and understandable metaprogramming features. Peter describes ten callbacks that happen when a module is included or extended or a class is inherited. He tells us to define all callbacks as singletons. A great use case for callbacks is when you create a class or module and you find yourself starting to add complex usage notes in the class or the app’s README telling users that they will need to implement various methods. Instead of telling the programmer what code must be implemented, hooks let you code those implementations, dynamically, for them. Peter’s example for this item references back to item 21 where it was stated: “You should write a initialize_copy method that duplicates the delegation target and also implement freeze, taint, and untaint.“ In item 28, Peter’s example is a module called SuperForwardable that implements those methods whenever you write a class that contains: extend(‘SuperForwardable’).
29: Dad Chaining Oil
As I was hanging the phone back up on the hook, I noticed, out the window, that my father was changing the oil on my car. What a super dad. He was so sure I wouldn’t follow his advice and change my oil every 5,000 miles that he’s doing it himself. I looked at my Dad’s class implementation and I saw:
class CyrilDenoncourt
def self.inherited(son)
super
puts "change oil every 5000 miles for #{son}"
end
end
class RobertDenoncourt < CyrilDenoncourt
def self.inherited(son)
super
end
end
class DonDenoncourt < RobertDenoncourt
end
Wait, it isn’t my Dad (Robert,) but his Dad (Cyril,) that is managing my oil change. I’ll looked back out the window and, yup, there was Grandpa, changing my oil. And him at one hundred and fourteen years old!
The above example only used one of the callbacks available for classes. Peter suggests all of your class callback implementations should invoke super.
30: X-Files: Missing Persons
On the wall in that office I saw an X-Files poster. It was what some people call the “Defining Episode.” Essentially Moulder and Sully figure out that there’s little need for a Missing Persons department if behavior was well defined.
You must watch the episode (and read Effective Ruby,) but, what it comes down to is that writing missing_method implementations is usually not the best way to perform metaprogramming. Peter tells (and shows) us there is almost always a way to use define_method to dynamically add a method implementation rather than writing a confusing method_missing. Effective Ruby provides several concise and cool examples that make use of the public_methods, public_instance_methods, define_method and send methods. Peter also says that, if you must use method_missing, consider writing a respond_to_missing? method.
31: Judge Rudy and the Gallows
Back in the big hallway I saw Judge Judy and a gallows. Actually, it wasn’t Judge Judy. It was her twin sister: Judge Ruby. In front of both the gallows and Judge Ruby’s courtroom there were instances of people of both occupant and high class pedigree.
Judge Ruby
I watched with fascination as, one-by-one, case instances would be eval-uated by Judge Ruby. Each of them were holding either a string or a block. The strings would interpolated and then eval-uated by Judge Ruby. I was a bit concerned about the interpolation of strings because I saw one strand that went out of the courtroom and terminated with a connection to one of those dynamite plunger boxes so often used by Wile E. Coyote in the Road Runner cartoon: https://www.google.com/imgres?imgurl=https%3A%2F%2Fc2.staticflickr.com%2F4%2F3405%2F3179621871_5aff6f725a_b.jpg&imgrefurl=https%3A%2F%2Fwww.flickr.com%2Fphotos%2Fbjacques%2F3179621871&docid=_Y_cNnaae7EtkM&tbnid=kVn_hfbd7Ot16M%3A&vet=10ahUKEwjuu7XwrsTYAhUkT98KHWATCSwQMwhbKAIwAg..i&w=683&h=1024&itg=1&hl=en&safe=active&bih=916&biw=1499&q=dynamite%20plunger%20box&ved=0ahUKEwjuu7XwrsTYAhUkT98KHWATCSwQMwhbKAIwAg&iact=mrc&uact=8. The eval-uation of blocks by Judge Ruby seemed more safe to me. None of the cases were given the opportunity to give arguments. Judge Ruby would evaluate each case based on their context, or in other words, their inner-self.
I was starstruck by one of the case instance... it was none other than Colonel Homer. He was dressed all in white and had a white ten gallon hat on. (see http://www.gstatic.com/tv/thumb/v22episodes/1299689/p1299689_e_v8_ab.jpg).. When Judge Ruby evaluates Colonel Homer’s cases, pretty much anything goes. But, here’s the thing, even though he’s a kernel, he still is, after all, Homer and will stupidly eval and do the string of words whispered by anybody into his ear. Perhaps that is why some of the Colonel Homer instances were bound by cords binding pieces of context to them -- to limit scope of what he can do. Colonel Homer, by the way, doesn’t comprehend blocks and only deals with strings.
The Gallows
If the goings-on in Judge Ruby’s courtroom bothered you, believe it or not, you’ll find the Gallows less troublesome. One instance at a time was being exec-uted. But, unlike those being eval-uated by Judge Ruby, these guys would be asked: "Do you have any last arguments?" by their executioner. It was good to know that these guys were mercifully allowed arguments as they were block exec-uted.
Take-away
The main differences between the various eval and exec methods is that
1) eval takes strings and blocks; exec only takes blocks
3) exec does accept arguments; eval does not
4) Kernel#eval runs in the current context, whereas the other evals run in the context of their receiver.
Besides providing some thought-provoking eval and exec examples, Peter tells us 1) methods defined using instance_eval or instance_exec are singletons and 2) class_eval, module_eval, class_exec, and module_exec create instance methods. Peter also points out that the scope of Kernel#eval can be limited by passing an explicit Binding object.
32: Monkeys
I heard screeching as I began to move along the hallway and, as I turned a corner, I saw 32 intelligent looking monkeys wearing patched up bib overalls. Some were obviously far more refined. The refined ones kept to themselves with no observable monkeying around. As to the others, I was nervous about their sudden and unexpected behavior.
I used to call my monkey patching “open classing” thinking that verbiage suggested I was not monkeying around and rather had a valid reason to modify other people’s code in hidden places. Regardless of how intelligent you think your monkey patches are, Peter believes there are most always alternatives to monkey patching and provides a number of examples including module methods and use of Forwardable. But, if you feel you must monkey patch, Peter suggests use of refinements. Refinements must be activated with the using method before they can be used by a class. Peter does warn use that Ruby’s implementation of refinements are subject to changes.
[Why 32 monkeys? It’s just a way for me to remember what item number I’m at of Peter’s 48.]
33: Moby Dick
At the end of the monkey-infested hallway I saw a poster of of Moby Dick with the opening line: “Call me Ismael.” That is one of the most famous opening lines of any novel. Is his name really Ismael? Or is it an alias? It makes you want to find out more about alias Ismail and what his job has to do with Moby Dick? Who had that job before he took over? And does he still rely on that person? If Ismael reaches his dismize, will his predecessor resume performing the job tasks?
Peter considers alias chaining to be a safe form of monkey patching. But, with alias chaining, you can alter the behavior of an existing method implementation without affecting behavior of the target class. And, an alias chain can be reversed. Creating an alias chain is easy:
alias_method(original, the_new_one)
But Peter suggest we make sure the name of the aliased method is unique and to consider providing a method to reverse the alias chain.
34: Bomb Shelter:
Walking into the next room I see two arity raid bomb shelters: one built of cement blocks the other of straw blocks. One shelter was obviously built using strong procedures whereas the other used weak procedures.The builder of the strong shelter might complement themselves but the weak counts on itself.
Let me explain: If you write a method that takes a block, users of that method pass a block, lambda, or Proc -- all of which get converted to a Proc object. The problem is that some Procs are weak and some are strong. If you pass an unexpected number of arguments to a strong Proc, an ArgumentError will be raised. The weak Proc just sets missing argument values to nil. The Proc#arity method returns the number of mandatory arguments. Oddly, if it is a strong Proc with optional arguments, the Fixnum returned from Proc#arity might be negative. That negative value it is a unary complement. If you use the unary tilde operator on the result of Proc#arity, you receive the required parameter count. Peter simple solution to the prevention ArgumentErrors, is to get the Proc’s arity and then check it both with and without the tilde unary operator for the expected argument count:
param_count = block.arity
if param_count == 3 # weak Proc
|| ~param_count == 3 # strong Prod
# do stuff as we have all the required arguments
end
(For a proc-lambda-proc refresher: http://blog.lunarlogic.io/2017/ruby-refresher-proc-lambda-block/?utm_source=rubyweekly&utm_medium=email)
35: The Great Prepender
As I stood there looking at the two arity raid shelters. I heard music. It was a recording of the famous song “The Great Prepender” by the Platters (https://www.youtube.com/watch?v=FyM8NVl4yBY). You know:
Oh yes I'm the great prepender (ooh ooh)
Pretending I'm doing well (ooh ooh)
Oh yes I'm the great prepender (ooh ooh)
Adrift in a world of my own (ooh ooh)
Ooh ooh yes I'm the great prepender (ooh ooh)
Just laughing and gay like a clown (ooh ooh)
But Peter is not a fan of module prepending. If you prepend a module, rather than include, it inserts the module before the receiver putting the inheritance tree in a unexpected structure.
Chapter 6: Testing
36: Mini-bar
Ah look, a mini-bar refrigerator! Time for a drink. The cooler had the brand name of MiniTest and model name of Assertions. I opened the door and was disappointed to not see any drinks. Instead I saw shelves labeled with prefixes of test_. Each shelf contained a bit of stuff and one or two assertions (or refutations .)
Peter is a MiniTest fan, he tells us 1) test methods must begin with the “test_” prefix, 2) keep our test methods brief, 3) Use the most descriptive assertions, and 4) assertions and refutations are documented in the MiniTest::Assertions module
37: eSpectations
Moving on from the refreshment-less mini-bar, I saw a kiosk for spectacles that had a Harry Potter emblem. The signboard said “eSpections: a division of MiniTest” followed with then nauseating tagline: “Our eSpectation is that you’ll get what you need if you describe what you want”
The MiniTest::Expectation module supports Spec testing where you describe your test in more of an English-like script than raw Ruby code. Peter says to use the use the describe method to create a test class and then it methods to define your test. He says that while assertions are still available, it’s more descriptive to use the expectation methods. He further suggests looking at RSpec or Cucumber, if you like the spec testing DSL syntax. Me? I’m an RSpec fan but it is good to be familiar with the MiniTest mechanisms as well.
38: Trump: Alec Baldwin
I opted out of acquiring spectacles and move on. At my next stop I saw one of my favorite people pontificating -- President Trump. Actually, it wasn’t Trump, it was Alec Baldwin mocking Trump, Trump is actually a bit lower on my favorite person list. Anyway, Alec does a great job of, using Peter’s terminology: “mocking to isolate the nondeterministic world.” I mean, who knows what Trump is going to tweet next? Peter warns us of the obvious: “mocking or replacing a method might lead to untested code that could fail in production.” He also suggests the use of MiniTest::Mock#verify.
39: Sully
Chuckling about one of Alec Baldwin’s bits, I walked to the next item of interest. Sitting in front of a flight simulator was none other than Sully, the airline pilot that saved all those people in the crash on the Hudson river. (Actually, it was Tom Hanks -- I don’t know what Chesley Sullenberger looks like.) Anyway, Sully was testing himself on every problematic flight pattern that could be thought of by flight simulator developers. It was Sully’s dedication to the philosophy and practice of constant testing that gave him the capability to save all those people.
Peter tells us to coerce both happy and exception paths, to consider additional test tooling, and to automate the execution of test. He warns us that although code-coverage stats are generally a good thing, they can make you overly confident that code is correct simply because it is well tested. Peter also tells us to write tests before fixing bugs.
Chapter 7: Tools and Libraries
40: What’s Up, Doc?
I was feeling a bit wonky after all that unit testing stuff and was ready for a breather. So I thought I’d visit the office of my old coworker, Doctor McCoy. McCoy actually had a medical degree but, after retiring from practicing medical, he went back to his first love -- programming. We actually called Leonard, “Bones,” to jest about his medical degree but he could code at warp speed. Bones had dozens of books on his office wall of his office and he was also a prolific writer. I popped into his office plopped down in a chair and asked “Why do you bother writing?” Bones said: “You read to learn, you write to understand.” Then he said: “You’re doing Ruby now. Remember: ’ri reads and RDoc writes’”.
We all know that Ruby is bundled with the ri and rdoc tools. They are both crazy easy to use. After reading Peter’s book, I started using ri more. Turns out… I was googling far to much for information I could find much, much more quickly with ri. And I was also scanning code when using ri on my application code would also be much, much quicker. You generate RDoc to be used with the ri command with the -f ri option. As you use ri more and more you’ll want to use it for your application gems code as well. That is easily done with: gem rdoc --all --ri --no-rdoc. And if you want to know more about RDoc formatting rules, don’t waste time googling, use: ri RDoc::Markup.
41: Playground
As I left Bones’ office, I heard children playing. I had heard that the offices were thinking about adding daycare. The kids sounded like they were having so much fun, I had to pry and take a peek. They were playing with building blocks. They’d build something amazing, laugh, crash it, and try a different structure.
We all need play. We learn from play. Ruby has its own playground: irb. You should go into irb (or the Rails console) at the drop of a hat. Curious about a Ruby feature? Try it in irb. Start to push the limits of irb. For example: Did you know that you can write your own IRB commands in the IRB::ExtendedCommandBundle and put your code in the .ribc file of your home directory. Peter points out a number of tricks and the one I started using is that, within an irb session, you can use the irb command to stack-start a new session passing an object that becomes the default context.
42: Bag of Tools
I walked into a different section of the offices and I saw bundle of tools.
You know this one. It’s bundler. Peter says, for apps, check in Gemfile.lock as well as Gemfile but, for gems, do not track Gemfile.lock.
43: Jewelry Advertisement
Looking up from the bundle of tools, I saw a back cover of a magazine on a desk. It featured an attractive model wearing expensive gems: a ring, bracelet, and necklace. Those kind of advertisements really aggravate me. They coerce people to going out of their financial boundaries.
Ruby gems all too easily can go out of bounds. Peter strongly suggests the use of declaring upper bounds in your Gemfile. Reading between the lines, to paraphrase Peter, he’s basically saying: “No upper bound is stupid.” Most of the Rails applications I’ve worked on use the pessimistic version operator (the tilde) such as:
gem (‘money’, ‘~> 5.1.0’)
Peter suggest the use of an explicit range (or even limit to a specific version) because you don’t know your code is going to work with all future versions of a gem. That said, when creating a gem, try to specify a range that is as wide as is safely possible.
Chapter 8: Memory Management and Performance
44: Hoarder
As I walk into the next room I see a complete mess. There are heaps and heaps of pages with slots filled with various and sundry objects. Right before my eyes, the hoard of stuff was expanding… at an alarming rate. Probably because folks kept coming by and heaving stuff up onto the heap. If it were not for this guy, some fellow wearing a zip-up coverall suit, the room would have exploded from a glut of junk. This coverall-clad worker, with a name tag of George Cooper (let’s call him GC,) was marking things with a felt-tip identifying them as useful and then sweeping up and disposing the unmarked items. I took notice that, after one of GC’s cycles of marking and sweeping, some of the newer objects were labeled “old” by GC when he had seen them in a previous cycle.
I continued to watch, fascinated, for quite a while (probably for hundreds and hundreds of milliseconds.) His behavior varied somewhat on his marking and sweeping phases. When the room was getting close to full, he would make a major effort to carefully look at all the objects, young or old, and mark them if they were useful. Otherwise his effort was more minor and he’d ignore the old objects during his marking phase.
GC’s behavior varied as well for sweeping up. Most often GC would immediately begin to dispose of any object, regardless of their your or old flag, if they lacked a useful mark. But whenever some kind person would come by and launch a new object into the heap, GC would get irritated and go into lazy mode where he would only remove a minimal amount of stuff.
To summarize, GC used a mark and sweep strategy. In the marking phase he’d make either a major effort to mark everything or minor effort to, quickly as possible, mark only new objects. His sweep phase would either be immediate, where everything not marked as in-use was remove; or lazy, where he disposed a minimal amount of objects.
Peter’s description is obviously much more detailed. Then he points out the invoking GC::stat will generate a hash with detailed statistics about garbage collection. He also wants us to know that we can tune GC specific to your application’s behavior with environment variables.
45: ObjectSpace Destroyer Finalizer
Finally I was able to pull myself away from the “Hoarders Live” reality show and enter the next office. Opening the door I saw, hovering mid air, an ObjectSpace Destroyer Finalizer. It was triangular shaped, just like a Star Destroyer Finalizer (https://www.youtube.com/watch?v=uSIeseHtcao ). And, it too, was heavily manned and armed. But, whereas the Star Destroyer Finalizer was designed by the Evil Empire, the ObjectSpace Destroyer Finalizer eradicated things for the greater good of optimized resources. One extremely strange thing about the scene before me was that, underneath the hovering Destroyer, there were lambs grazing.
A man in a cloak welcomed me as I stepped into the hangar. He thanked me quietly for my efforts against the dark forces of bad code. Then he told me my use of the ObjectSpace Destroyer Finalizer was available but I should strive to ensure the simplest approach first.
Peter introduces us to ObjectSpace.define_finalizer for cleaning up the use of resources, which, by the way takes a lambda or Proc to be called after an object has been destroyed. Just as the cloaked man said, Peter recommends the use of ensure clauses (see item 24) for the release of resources. But, if you must expose a resource outside of an ensure clause, a finalizer should be used.
46: MindHunters
I must have hit a time warp because, when I walked into the next office, based on the furniture, I could tell it was office from the late 1970s. There was a conference going on with a couple guys that, obviously, from their cheap dark suits, were some kind of cops. They were using a slide projector and, on the wall, they were viewing photos of Ed Kemper, Jerry Brudos, and David Berkowitz. I then knew who these cops were: They were the FBI’s first team of profilers. Instead of optimizing society by apprehending deviants after crimes happened, they sought to understand their minds. The thought was that, if you understood them, you could prevent future derelict behavior.
Peter tells us we should profile code before spending time on optimization. He covers some general concepts on their use but, mostly, he just wants you to be aware of the availability of bundled and easily acquired profiling tools such as stackprof (https://github.com/tmm1/stackprof) and memory_profiler (https://github.com/SamSaffron/memory_profiler).
47: Cookie Cutter
The smell in the next room was intoxicating, someone was making cookies! I stepped in and saw a man looking intently at a whiteboard of instructions. It detailed the steps necessary to make a dozen kitty cat shaped sugar cookies. A kitty cat cookie cutter was hanging on the wall next to the whiteboard. Because I wanted a fresh cookie, I decided to stick around. The cook expertly mixed and then rolled out the dough. He then took the cookie cutter off the wall, put it on a sheet of paper, penciled out a kitty cat shape, put the cookie cutter back on the wall. Next he cut out the pencilled shape and used it as a template on the rolled out cookie dough to cut out one cookie with a knife. He repeated the paper cat and cookie cut out process until he ran out of dough. As a result of this maddenly slow process, in order to cut out twelve cookies, he had made a dozen paper template copies of the cookie cutter.
Peter provided a couple innocuous looking examples of strings and array literals that -- when used in loops that, like the cookie cutter above -- are duplicated numerous times. Peter reminds us that loops are often disguised in our Ruby code as blocks. One solution is to promote the object literals (which are not expected to be changed) to a constant. Another solution, available since Ruby 2.1, is to use the freeze method on strings in your loop as Ruby then makes them equivalent to constants.
48: Be Cool or Be Nothing
As I left the kitchen, eating a sugar cookie that almost looked like a kitty cat, I saw a poster of Humphrey Bogart with the meme: “Be cool or be nothing.” Underneath that it said: “Already done is more equal to do it once.” What? More equal? More equal… mORe Equal! Ah:
@already_done ||= do_it_once
That’s it -- memoization. But what does “Be cool or be nothing” mean?
Peter tells us to memoize expensive computations. But he then warns us to consider the side effects of memoization. If callers are not to modify a cached variable, consider returning a frozen object otherwise, if callers might modify the object, he suggests adding a method that sets the memoize object to nil. That’s right! “Be cool or be nothing.”
1 comment:
Hi Don, thank you for such a detailed post. It's taken me some trouble to find someone writing in detail and with examples on how to use memory techniques and specifically memory palaces as a means to learning/remembering coding. I'm very sure that there are a lot of people looking for the same thing but are unlikely to find this article.
I suggest that you modify the title or at least add a line in the text clearly stating that this is a practical example of using the memory palace technique to learning or memorizing code. Hopefully that would bring more people like me directly to this content. Thanks!
Post a Comment