More Prevention of NoMethodErrors on nil

Geplaatst door Michiel de Mare

What a sudden interest in avoiding NoMethodErrors on nil lately! I’ve just thought of another method. (Remember, you can solve any problem by adding a layer of indirection.)

Say you have Remco’s canonical example: person.name.upcase

Both person and person.name may be nil. So we usually write: person && person.name && person.name.upcase .

rrss

class Object def rrss yield self end end

rrss

person.rrss {|p| p.name}.rrss {|n| n.upcase}

to_proc

person.rrss(&:name).rrss(&:upcase)

person.rrss {|p| p && p.name}. rrss {|n| n && n.upcase}

Now for the level of indirection. Remember? I’ve introduced it last month. It stands for “returns result, same self”. Its definition is beyond trivial:Withwe can rewrite the original as:Or withnotation:. We still need to check for nil though:

Doesn’t this last bit look like it’s in need of a bit of refactoring? Its lack of DRY-ness jumps right out of the page.

x = lambda {|p| p && p.name} y = lambda {|n| n && n.upcase} person.rrss(&x).rrss(&y)

arr = [lambda {|p| p && p.name}, lambda {|n| n && n.upcase}] arr.inject(person) {|o,l| o.rrss(&l) }

arr = [lambda {|p| p.name}, lambda {|n| n.upcase}] arr.inject(person) {|o,l| o && o.rrss(&l) }

arr = [lambda(&:name), lambda(&:upcase)] arr.inject(person) {|o,l| o && o.rrss(&l) }

to_proc

arr = [:name,:upcase] arr.inject(person) {|o,l| o && o.rrss(&l) }

class Object def chain(*a) a.inject(self) {|o,l| o && o.rrss(&l) } end end

# long ago person && person.name && person.name.upcase # now person.chain(:name,:upcase) # with obedient nil # (in alternate, bugless universe) person.name.upcase

First try:Let’s use an array instead!We should use the nil test to within the inject block.Hey, can’t we write those lambdas a lot nicer?Do we even need lambdas? We’re calling them with the ampersand(&), sowill be called on them anyway.Let’s turn this into a method!We’ve made quite a journey. But look, we’ve come a long way!

Why don’t we go a little further yet. What if we’re not worried about nil . What if the want to stop a calculation as soon as it’s zero?

result = if value == 0 then value else tmp = calc(value) if tmp == 0 then tmp else more_calc(tmp) end end

We could write another method on object to handle just this. But let’s make it more generic. Let’s accept a block.

class Object def chain(*arr) arr.inject(self) do |o,lmb| if block_given? yield o,lmb else o && o.rrss(&lmb) end end end end

Symbol#to_proc

chain = [lambda {|v| calc(v) }, lambda {|v| more_calc(v)}]

value.chain(*chain) do |v,lmb| v == 0 ? v : lmb[v] end

How does this work in practice? First, we need to define the chain, andwon’t cut it anymore. We’ll need lambdas.Lambdas are ugly, but even so, that wasn’t so bad. Now, let’s use it!

Of course, the more steps you have, the bigger the gains of this method. With two steps, it’s a toss up. With three of more steps, you can go a long way in cleaning up your code.