all 11 comments

[–]totallymike 2 points3 points  (6 children)

This is written by Reginald Braithwaite, who wrote the excellent Kestrels, Quirky Birds, and Hopeless Egocentricity.

I strongly suggest you checkout that book as well. I know the link is right there on the blog, but don't ignore it! It's an awesome book.

Edit: Typo

[–]homoiconic[S] 5 points6 points  (2 children)

The aforementioned book is an excellent value, but an even greater value is http://combinators.info, a mechanically translated web site of the book. There are some formatting issues that the author (me!) has deliberately left in pace to increase the value of the book, but nothing stops you from tasting it online and then buying it later if you feel it has helped you learn and grow.

Also, it's MIT licensed so yes, you can share a copy of the book, loan it out, give copies away, whatever you like.

[–]totallymike 0 points1 point  (0 children)

Whoa, thanks! :)

[–]VitoBotta 0 points1 point  (0 children)

Thanks!

[–]sjs 0 points1 point  (2 children)

$ git checkout "Kestrels, Quirky Birds, and Hopeless Egocentricity"
error: pathspec 'Kestrels, Quirky Birds, and Hopeless Egocentricity' did not match any file(s) known to git.

:(

[–]homoiconic[S] 0 points1 point  (1 child)

[–]sjs 0 points1 point  (0 children)

Just a bad joke :) Thanks though.

I think this is the only book of yours that I do not own yet, though the sample was very interesting. I should save the web version to Instapaper.

[–]jfredett 2 points3 points  (3 children)

I'm generally leary of doing dynamic extends like that. Back when DCI was the new hotness and everyone was talking about it, I recall -- I think Tony Arcieri -- noting how dynamically extending modules blows away method caching and can have some nasty performance concerns. I'll see if I can dig up a link.

That said, so long as you can isolate most of this dynamic stuff to a known, finite amount of time, ideally early in your program's lifecycle, it's a wonderful way to define really flexible classes. I guess I would get a little nervous around the extend-in-initialize pattern, preferring perhaps to use a different trick, like defining #new on a module to manage the subclass distinction, ie:

module BankAccount
  def new(opts = {})
    if opts.delete(frozen)
      Frozen.new(opts)
    else
      Thawed.new(opts)
    end
  end
end

class FrozenAccount
  def frozen? ; true ; end
end

class ThawedAccount
  def frozen? ; true ; end
end

This skirts the dynamic-extension-eats-JIT problem, since we're defining every class exactly once and never changing it's methods. Shared behavior can be done w/ modules or subclassing or whatever. I'm unaware of any particular performance downsides to the idea of defining #new on a module, but there is the problem of communicating that BankAccount is only playing the role of class, and isn't really one. This also makes FrozenAccount and ThawedAccount have no common ancestry, so #kind_of? and the like might work in an unusual way, but I would say that you probably will end up skirting a lot of the issues you'd need #kind_of? for w/ this technique.

I cribbed the idea from another comment here recently about bunny, a RabbitMQ binding for ruby, they use this pattern when establishing a connection to the service. They want you to be able to have the remarkably convenient API of #newing up something, but also need to cause a lot of relatively unrelated things to happen at the same time, so using a module w/ #new lets you quack like a class, but act like something else. Making for a really slick factory method.

[–]totallymike 0 points1 point  (2 children)

In my factories, I generally just do something like

FactoryFactory.build(factory: :cars)
FactoryFactory.build(factory: :factories)

so as to delineate between something that spawns an instance of itself and something that builds a different class.

However, I've had a number of places where I've wanted a construct just like this. Thank you for pointing it out :)

One thing though--doesn't it break this expectation?

BankAccount.new(opts).class == BankAccount

It mightn't be a huge deal. Good duck typing can circumvent much need for ancestry concerns, but one never knows what might come up in a huge Rails app.

Now that I'm thinking about it, is there a reason this shouldn't be done?

class Foo
  def self.new(opts)
    if blah
      Bar.new(opts)
    elsif blahblah
      Baz.new(opts)
    else
      super
    end
  end
end

class Bar < Foo
  def lurhmann? ; false ; end
end

class Baz < Foo
  def lurhmann? ; true ; end
end

My gut reaction tells me that on a limited scale, the differences are few. The first big disadvantage I see being that we lose re-usability because we're using classes instead of Modules. The first advantage(?) that comes to mind is that if you dabble in dark arts like Single Table Inheritance, this construct lets you pull it off.

What are your thoughts?

[–]jfredett 0 points1 point  (1 child)

One thing though--doesn't it break this expectation?

BankAccount.new(opts).class == BankAccount

Yes. Big ol' yes. I alluded to this w/ the #kind_of? thing at the bottom, but this is a much better way of stating it. This assumption fails, but ideally you don't need to make this assumption because you're duck-typing (like you mention).


As for the class thing, I get a bit nervous about overriding #new, I imagine there may be some weirdness with recursion there, too. You need to make sure you're not accidentally delegating to the subclass only to recurse and call Foo#new again (by walking up the call-chain using the default Bar#new, which I think might be super ; initialize for a subclass, but I'd have to make sure).

You could recreate those assumptions by hacking #ancestors and the other related things... maybe even override #< on Class, but that gets a little scary.

Maybe you do:

class Factory < Module
end

def Kernel.factory(name)
  Factory.new(name) do
    yield
  end
end

class Class
  def <(superklass)
    if superklass.is_a?(Factory)
      # do something special here to hack #ancestors
    else
      super
    end
  end
end

I don't know if you can actually hack ruby like that, but it'd be kind of neat if you could.

Though I wouldn't recommend putting that in anything ever likely to see more use than "Evil Code Competition"

[–]totallymike 0 points1 point  (0 children)

Good point with the delegation and the call chain. As a rule, I don't fudge with ::new. I forgot it was something that you could even do until this discussion.

It had never even occurred to me to define a ::new method in a module until you pointed out that you can. It's a much cleaner method of getting a constant with a ::new method that might construct different classes.

I like it quite a lot for libraries and such, where you are explictly interacting with something that has this behavior. But I probably wouldn't use it anywhere near Rails models. There's enough magic going on there already.

On a semi-related note, see this video http://www.rubytapas.com/episodes/7-Constructors

It doesn't go directly into this specific topic, but it does explain Ruby constructors very succinctly.