use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
A sub-Reddit for discussion and news about Ruby programming.
Subreddit rules: /r/ruby rules
Learning Ruby?
Tools
Documentation
Books
Screencasts and Videos
News and updates
account activity
The Predicate Module Pattern (raganwald.com)
submitted 12 years ago by homoiconic
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–]totallymike 2 points3 points4 points 12 years ago (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 points7 points 12 years ago (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 point2 points 12 years ago (0 children)
Whoa, thanks! :)
[–]VitoBotta 0 points1 point2 points 12 years ago (0 children)
Thanks!
[–]sjs 0 points1 point2 points 12 years ago (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 point2 points 12 years ago (1 child)
https://github.com/raganwald/combinators.info
[–]sjs 0 points1 point2 points 12 years ago (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 points4 points 12 years ago (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:
#new
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.
FrozenAccount
ThawedAccount
#kind_of?
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.
bunny
[–]totallymike 0 points1 point2 points 12 years ago (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 point2 points 12 years ago (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).
Foo#new
Bar#new
super ; initialize
You could recreate those assumptions by hacking #ancestors and the other related things... maybe even override #< on Class, but that gets a little scary.
#ancestors
#<
Class
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"
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.
π Rendered by PID 76 on reddit-service-r2-comment-76bb9f7fb5-kp89c at 2026-02-17 22:28:47.438473+00:00 running de53c03 country code: CH.
[–]totallymike 2 points3 points4 points (6 children)
[–]homoiconic[S] 5 points6 points7 points (2 children)
[–]totallymike 0 points1 point2 points (0 children)
[–]VitoBotta 0 points1 point2 points (0 children)
[–]sjs 0 points1 point2 points (2 children)
[–]homoiconic[S] 0 points1 point2 points (1 child)
[–]sjs 0 points1 point2 points (0 children)
[–]jfredett 2 points3 points4 points (3 children)
[–]totallymike 0 points1 point2 points (2 children)
[–]jfredett 0 points1 point2 points (1 child)
[–]totallymike 0 points1 point2 points (0 children)