You are viewing chalain

   Journal    Friends    Archive    Profile    Memories
 

Chalain - Turtles!

Feb. 1st, 2008 08:28 pm Turtles!

Sometimes smart answers produce dumb code.

I wrote Reg an e-mail about the Object#if_not_nil misunderstanding, and while discussing it I pointed out that his Object#andand code only works one level down. This code would work:

puts Store.find(...).andand.location
but this code would not:
puts Store.find(...).andand.city.name
If Store.find() returns nil, andand will catch it, but city will then be nil and city.name will raise a NoMethodErorr in NilClass. Obviously, we could add andand's to each level of the chain, but for long chains this becomes almost perverse:
puts Store.find(...).andand.city.andand.region.andand.country.andand.name
It seems to me that we need some way to arrange things so that if, when traversing a guarded chain, and you hit a guarded link that returns nil, if you continue traversing the chain, you will still be guarded. Invoking a method on a nil object returns a nil object, and invoking a method on that nil object returns another nil object, and so on and so on.

It's turtles all the way down!

And so it was that I wrote the Turtles module.

Turtles works very much like Reg's Object#andand, only it is somewhat more convenient, considerably more dangerous, and altogether more absurd. You alert the reader that you are doing operations with turtles simply by placing code in a with_turtles block:
with_turtles do
  puts Store.find(...).location
  puts Store.find(...).city.name
  puts Store.find(...).city.region.country.name
end
or inline:
with_turtles { puts Store.find(...).location }
Turtles is also smart enough to handle blocks without freaking out:
>> arr = [1,2,3]
>> x = with_turtles { arr.collect { |f| f * 2 } }
=> [2, 4, 6]
>> arr = nil
>> x = with_turtles { arr.collect { |f| f * 2 } }
=> nil
The thing I love best about turtles mode is that you can turn on turtles for your entire application:
turtles!
puts Store.find(...).city.region.country.name
and you can turn turtles mode back off with the equally appropriately-named no_turtles! command:
no_turtles!
Now, defenders of Reg's code might point out that with_turtles is not, in fact more convenient than andand but on a character-by-character basis I think you will find that with_turtles is the clear winner if the chains are longer than 1 level deep. But I don't think too many people will disagree with me that the global turtles! mode is as considerably more dangerous as advertised.

The best thing about this code is... well, there are two best parts. The first best part is that now you can cobble together expressions that chain together every single object in your system, safe in the knowledge that the turtles are watching over you even as your entire application becomes a single, monolithic mass of fused code.

And the second best part, and really, this is the better best part, is getting to start your programs like this:
#!/usr/bin/env ruby -w

turtles!


I mean seriously. turtles! What is not to love?!?

turtles.rb
turtles_spec.rb
Tags: ,

Current Mood: sillysilly
Current Music: Living in a Bubble - Eiffel 65

22 comments - Leave a commentPrevious Entry Share Next Entry

Comments:

From:chalain
Date:February 2nd, 2008 04:16 am (UTC)
(Link)
P.S. HAHAHA! Random music selection FTW again.

Also, this is the best code I think I have ever written in my life:

no_turtles! unless already_turtles
From:jfargo
Date:February 2nd, 2008 04:47 am (UTC)
(Link)
The layman's reaction:

"I like turtles!"
From:bibliophage
Date:February 2nd, 2008 06:24 am (UTC)
(Link)
Don't you mean tortoises, since you're not talking about a functioning involving aquatic behavior?

if pond, then turtles, else tortoises?

From:darthparadox
Date:February 2nd, 2008 06:37 am (UTC)
(Link)
There may be a pond at the bottom, or maybe not. We don't know; it's turtles all the way down.
From:strredwolf
Date:February 2nd, 2008 03:15 pm (UTC)

What's not to like?

(Link)
They're not kittehs. :)
From:chalain
Date:February 2nd, 2008 06:47 pm (UTC)

Re: What's not to like?

(Link)
HAHAHA! Just last night I was talking with Vornicus about this.

I don't know what the Kitties module does, but it exposes two methods:

1. omg!(obj) which takes a Kitties object as its argument and exits the program, and
2. kitties!() which takes no arguments and returns a Kitties object.

Remember that Ruby makes parentheses optional. This means that you can now exit a script at any time by calling
omg! kitties!
From:pnsm
Date:February 3rd, 2008 05:33 am (UTC)

Bwaha!

(Link)
That's absolutely hilarious. (Even my non-programming sweetie was amused.)

I've occasionally considered doing this, but never actually implemented it. I did once wind up doing something vaguely similar that looked like:

class Comment < ActiveRecord::Base
dummy_if_nil :author, DummyUser
end

I never would've thought to do turtles!, though. +1 for humor.
From:chalain
Date:February 3rd, 2008 10:48 pm (UTC)

Re: Bwaha!

(Link)
Yeah, I've been rolling around the idea for NilFoos objects for a while, even since I read about Ron Jeffries doing some agile project in Java where they had a Null version of every important class. So if you searched for a User, you either got a User or a NullUser object. Either way you were guaranteed that you would get back an object that acted like a user. NullUser did things like respond to name with "", etc.

turtles! is more sort of a "the logical conclusion is absurdity" thing, but a few people are taking it seriously--and I am tempted to make a gem and/or a plugin out of it. If I can keep a straight face, I may be able to turn this humor into a practical joke....
From:(Anonymous)
Date:February 3rd, 2008 06:46 pm (UTC)

Exceptions

(Link)
In with_turtles, shouldn't no_turtles! be in the ensure clause of the method? As it is, turtles are not reset when the block yielded to raises an exception.
From:chalain
Date:February 3rd, 2008 10:36 pm (UTC)

Re: Exceptions

(Link)
Brilliant! I've added a spec for it, and coded it up. Thanks!
From:openid.dweebd.com
Date:February 4th, 2008 05:39 pm (UTC)

Demeter

(Link)
This strikes me as spackle over a more serious issue. The turtled objects seem to have too much knowledge about methods more than a single step down the Demeter chain.

Sounds like it might be useful when writing a unit-testing framework around legacy code, but otherwise I'd steer clear.

Neat trick though!
From:chalain
Date:February 4th, 2008 06:56 pm (UTC)

Re: Demeter

(Link)
I can't decide if I am tickled or horrified that so many people are taking this code seriously.

It IS a neat trick, and it may even have its place. If I ever used this code, however, it would mean:

1. That the code blatantly violated the Law of Demeter.
2. That I was not in a position to refactor the LoD violation away.
3. That I didn't care enough about the project to fix it.

I direct your attention to the first sentence of the post. This is dumb code! Sure, Bobby Fischer once said, "There may not be a good move, but there is always a best move," and I can see that there might be times when turtles would be the best code you could use, but as I think about it they all fit into the 3 conditions I described above.
From:marnen
Date:March 24th, 2009 04:00 pm (UTC)

Re: Demeter

(Link)

Sorry for such a late comment, but I'm not sure I agree with your statement that null-guarding always implies a Demeter violation. I'd like to understand this better, but I don't at the moment.

Here's an example of a real case where I'm not sure there is a Demeter violation, or at least not a significant one. (I should preface this by saying that while I'm extremely dedicated to the best object modeling possible, I'm somewhat skeptical about the Law of Demeter -- I think it may be needlessly restrictive in some cases.)

The issue in question arose in implementing an instance method that should do some operations on a String-or-nil field of self (so I'm not playing with anyone else's toys here). Namely, it should return the lowercase String, or nil if the field is nil.

In other words, we have something like

class User
  def do_stuff
    @name.downcase
  end
end

Presumably this is playing with myself, and so not a Demeter violation. Now, if this is simply playing with myself, why is it suddenly a Demeter violation if we replace line 3 with self.name.check_for_nil!.downcase ? Am I taking too much apart? That seems arbitrary -- after all, it's not too much of a stretch of the imagination to think of defining NilClass#downcase as returning nil.

I guess what I'm asking is: if you think this is a Demeter violation, what would you do differently here? I suppose I could create a method User#name_in_lowercase, but that seems really ugly and not particularly scalable (I might need #address_in_lowercase, #name_in_uppercase, #name_with_html_escaped_in_title_case...you see where I'm going with this). I'm obviously missing some key concept here, but I'm not sure what that concept is.

From:marnen
Date:March 24th, 2009 04:37 pm (UTC)

Re: Demeter

(Link)
FWIW, see https://c2.com/cgi/wiki?LawOfDemeterRevisited for mostly similar arguments to mine, with some possible answers of how these views may be reconciled with LoD. (I didn't discover this page till after I wrote my first comment.)

In short, the C2 article suggests that the LoD may be better understood as a question of semantics than syntax, which I take to mean that my original example is arguably within the spirit, if not the letter, of Demeter, and thus I think my point about nil-guarding stands. But so many people seem to hate nil-guarding that I would really like to understand what the big deal is here.

I'm not trying to be antagonistic. I just genuinely don't know if I'm guilty of insufficient insight, or if "strong Demeter" advocates like yourself are guilty of cargo-cult programming. Or both. Or neither. :)
From:chalain
Date:March 24th, 2009 06:35 pm (UTC)

Re: Demeter

(Link)
Ahhh... yes. Okay, yes, I have unintentionally conflated Demeter violations with nil guarding in my writing.

I have absolutely no problem with nil guarding. We use Reg Braithewaite's wonderful andand library in our production code.

I think maybe someone could make the argument that @name.downcase is a LoD violation because Matz might change the definition of String, or because you might change the @name variable from a String to a Name object. I that this someone needs to be slapped with a ruler.

It only becomes an egregious LoD violation when I nil guard five layers deep. :-)

The opponents of nil guarding want you to always have valid objects. This is easily accomplished in your case: just initialize @name to "". It's just as empty (to the user's perception) as nil and you can downcase it without guarding. The problem comes when you try to build up that awful chain I wrote, of contact.city.state.country.name. The solution these guys come up with there is to initialize each object in the chain to an empty object. But coming up with a NullCity and a NullState (which would respond to all of City and State's methods) is pretty tricky and repetetive, so they end up writing a generic NullObject class. It responds to all methods by returning another NullObject.

Yay, that's great! They've avoided null guarding, right?

No they haven't. They've just hidden that null in the object model. They have an entire object system devoted to what our friend andand does for us.

As for cargo cult programming vs. sufficient insight. When you have done a major system refactor and had to tease apart a Demeter violation in 77 places throughout the code, then you will have sufficient insight. I have done this, and now I tend to favor Demeter. If have done a major refactor like this, I will respect your opinion regardless of what decision you make. :-)

I don't think I'm a cargo cult programmer, but then again, nobody ever does, so... maybe?

I will say that I generally favor of Demeter but not rabid about it. I will violate it from time to time, and every time I do I can see exactly how it places strain on the design in unpleasant ways. I'll use a LoD violation if it will get the job done most effectively, such as when we just don't have the time to split up that big class that has too many responsibilities. So I dunno, I think I'm less culty than pragmatic. :-)

I do tell programmers who don't know the difference to not violate it, and I may be guilty of spawing cargo culting there if they never try to figure out why. But there also seems to be a strong counter-Demeter sentiment out there that I just can't explain.

I haven't heard a good argument for why the LoD is supposed to be such a bad idea, other than people reacting to the part where it says "Law". There's something in the hacker spirit that objects to that sort of thing, I guess. It's not the kind of law that says "thou shalt not do this". It's more like the law of gravity: if you do this, your program will exhibit lower modularity and higher coupling. Cargo culters would say "we must never do that!" but I say "Eh, sometimes that's the right choice."

So yeah, in short: I think Demeter is a good idea, I'm comfortable with null guarding, and I really really like cheese.

Sorry, I sort of needed a third thing there to round out the triad.
From:chalain
Date:March 24th, 2009 06:00 pm (UTC)

Re: Demeter

(Link)
Hi Marnen!

I think you're right, that's not really a Demeter violation. You're only ever playing with your own toys there.

The way you make this a Demeter violation is to start grabbing other children's toys. If you want to print the User's country name and the User has a Contact, Contact belongs to City, City belongs to State, State belongs to Country and Country has a name (so we're talking about 5 different classes here):

def country_name
  with_turtles { @contact.city.state.country.name }
end


This code definitely makes Demeter sob in a corner. And I haven't even thrown the .downcase on the end yet. :-)
From:marnen
Date:March 24th, 2009 06:19 pm (UTC)

Re: Demeter

(Link)
Interesting. So I wonder why Demetrians tend to be the ones who hate nil-guarding so much.

It's stuff like your country_name example, though, that makes me wonder about the utility of the Law of Demeter outside the Demeter project. I would probably delegate several of these methods, but I don't really think I could justify telescoping any further than @contact.country.to_s : after all, what if a User's Contacts belong to different countries? (And if they don't have the potential to belong to different countries, then #country should be in User, not Contact!)

How would you improve country_name ? Would you improve it? Where would you consider the Demeter violations to lie? Or is Demeter just fundamentally incompatible with literate/fluent interfaces (which typically do lots of utility method chaining)?

My understanding is that Demeter got around the problem of excessive delegation partly by including some automated tools, but I'm still not convinced that such things really attack the issue at hand. (For that matter, Rails has Forwardable, about which I think the same objection can be made.)

So anyway...I think I still take issue with your statement that nil-guarding is always non-Demetrian (is that a word?), and that it is ipso facto bad because of that. I just don't see how to write real-world code while avoiding both nil-guarding and excessive delegation. If there is a pattern here I'm overlooking, please feel free to whack me with a clue-by-four!
From:chalain
Date:March 24th, 2009 07:03 pm (UTC)

Re: Demeter

(Link)
So anyway...I think I still take issue with your statement that nil-guarding is always non-Demetrian (is that a word?), and that it is ipso facto bad because of that.

You should take issue with it, because I agree with you. I don't think I actually made that statement. If I implied it, I did not I meant to. :-)

I just don't see how to write real-world code while avoiding both nil-guarding and excessive delegation.

Welcome to the rest of your programming career! These any many other variables will play in constant tension against one another. Choosing to violate the Law of Demeter will shift the values of many of these variables, and it will be your job to decide what the best overall state of the program should be.

If there is a pattern here I'm overlooking, please feel free to whack me with a clue-by-four!

Heh. Actually, the fact that you're asking these kinds of questions means that you're about to level as a programmer. (Make sure you have all your leveling gear on before your next grind.)

The epiphany that's coming at your head full steam right now is strategy versus tactics. Delegation vs. Proxying, Nil-guarding vs. RAI*... these are not good vs. evil choices so much as "appropriate to the situation" vs. "not".

And the answer to "how to fix the Contact example" question lies in strategy. Tactics won't work. There is no trick I can do in the code to make such a staggeringly stupid design work. So let's fix the design:

User has a Contact, Contact has City, State, and Country. We've flattened out the City->State and State->Country relationship for now, but that's the DBA's problem, not ours. :-) Now I would just use @contact.country.name And yes, I just broke the law of Demeter there a little bit, but I'm okay with it.

Now, if this code were part of a larger block of code that was ALL playing with Contact's toys, like

def mailing_address
str <<=EOS
#{@contact.first_name} #{@contact.last_name}
#{@contact.address1}
#{@contact.address2}
#{@contact.city}, #{@contact.state.abbrev} #{@contact.zip} #{@contact.country.name}
EOS
end


Then I think it's pretty clear that this method is totally living in the wrong place. It should be moved to contact. You could keep User#mailing_address if you really wanted, and just have it forward to contact, but to be honest I'd be tempted to just use @user.contact.mailing_address unless I had a good reason.

So... yeah. I'm a lot more likely to violate LoD if it's just ONE layer. I'll play with my friend's toys. Just not my friend's friend's toys....

* RAI: "Resource Allocation is Initialization" is a programming design in which you never ever have invalid objects. If you create a City object, and do not initialize it, it will still know enough to return a valid (but empty) string when you ask its name, and a 0 when you ask its population. When practiced in moderation it looks a lot like defensive programming. When practiced in extremes it becomes as clunky and unwieldy as extreme LoD. (For example, Person.bloodtype MUST return one of A, B, AB or O. What blood type should an uninitialized person have? Say you pick O. If you ever write a bug that accidentally admits uninitialized Person objects into the report, you've just silently overrepresented blood type O, and you have violated the law of Fail Early And Fail Loud.) So again, it's just another design choice that results in a tradeoff.
From:marnen
Date:March 24th, 2009 08:31 pm (UTC)

Re: Demeter

(Link)

You wrote:

If I implied it, I did not I meant to. :-)

Ah, OK. I was going from your statement "if I ever used [Turtles], it would mean I was violating LoD".

Heh. Actually, the fact that you're asking these kinds of questions means that you're about to level as a programmer. (Make sure you have all your leveling gear on before your next grind.)

Glad to hear you say that. Working with Ruby and Rails, as well as hanging around Ward's Wiki, has made me think harder about many things.

The epiphany that's coming at your head full steam right now is strategy versus tactics.

I'm not sure it's an epiphany so much as a continuous process -- I was trained as a composer, not a programmer, and so one of the things that I'm very aware of is that there's another level beyond theory. But especially since I'm self-taught as a programmer, it's always nice to have some confirmation that I'm at least asking the right questions. :)

Anyway, thanks for your comprehensive explanation. It looks like, at least in practice, I've been approaching these issues more or less the way you recommend.

From:marnen
Date:March 24th, 2009 06:41 pm (UTC)

More Demeter ramblings

(Link)
Another thought occurs to me. Is LoD really necessary in a duck-typed language like Ruby? Consider the conceptual differences between this Java code:
class User {
  Contact contact;

  String getCountryName() {
    return this.contact.getCity().getState().getCountry().getName()
  }
}

class Contact {
  City getCity();
}

// likewise for State and Country

...and the Ruby example you gave. They look similar, but really they're not, because of Java and Ruby's different semantics for typing and method invocation.

The Java example really does have to know too much about every step of the chain: it is explicitly asserting that Contact.getCity() will return a State, that State.getCountry() will return a Country, and that Country.getName() will return a String. The Ruby example, however, is asserting no such thing. It is simply asserting that each step in the chain will not raise MethodNotFound -- for all the caller knows, there might be some code somewhere that says
class << @contact.city
  def method_missing(*args)
    return self
  end
end

and yet the trainwreck would still work.

In other words, I think there's a case to be made that method chaining in a message-passing, duck-typed language like Ruby implies a lot less coupling than in a function-calling, strongly-typed language like Java, and so Law of Demeter (which was first formulated in C++, right?) may need modification for Ruby. Your thoughts?
From:chalain
Date:March 24th, 2009 07:19 pm (UTC)

Re: More Demeter ramblings

(Link)
Hmm... okay, my first reaction is "no way", but I'm not sure why. Just take my word for it, kid, and stack the crates higher on the north side of the runway.

Hmm... oh, wow. Okay, I think maybe it's actually a "reverse feature". It doesn't really relieve you of the implications of coupling, it just makes it harder to feel the symptoms. I think this might be a dangerous thing. :-)

So let's say we flattened Contact to have City, State and Country. Now we have to find all the code that uses city.state.country.name and change it. The Java code will break because it no longer compiles. The Ruby code will break because your tests/specs/features won't run. (TATFT, baby!)

Ultimately, the pain of a LoD violation comes when you change the interface of a class somewhere in the chain. In the case of flattening contact, you can see that it breaks code in both languages.

The Ruby code does the same thing the Java code does, really:

Java: getCity() returns a State object, which has a getCountry() method

Ruby: city returns a State object, which responds to :country

But if city suddenly returns a String because the state has been moved to Contact#state, your code breaks, no matter what language you're in.

Now go hunt down the 77 other places in your code where you call contact.city.state and change them. Your code won't run until you do. Oh, and don't miss the places that do it this way:

c = user.city
c.state


Welcome to coupling hell. :-)
From:marnen
Date:March 24th, 2009 08:33 pm (UTC)

Re: More Demeter ramblings

(Link)
Fair enough. I think I was asking more of a devil's-advocate sort of question than anything I'd actually do. (Although I still disagree with your statement that the Ruby #state method asserts that it returns a State object.) Yes, the code would still break, at least in most cases.

Hmm. So perhaps duck-typing violates fail-fast? There are some amusing examples on Ward's wiki about Spraypaint.spray versus Cat.spray...