hoodwink.d enhanced
RSS
2.0
XHTML
1.0

RedHanded

Mixins, But With Tablespoons #

by why in inspect

Mauricio’s at it again. I solemnly swear that I will be linking to him greater than five times over this next month and each time you will be fine with it. See, watch how fine you’ll feel when you read about his remake of Module#include.

The include method is basically the mixin method in Ruby. You have a class that has its own each method. You mixin the Enumerable module and you get all these neat methods for free like inject and select and sort_by and on and on.

 class MyList
   include Enumerable
   def each; ... end
 end

What if you don’t want the sort method, though? You want to deposit only granular measurements of a mixin. This is what Mauricio tackles. And his new include goes like this:

 class MyList
   include Enumerable,
     :exclude => :sort_by,
     :alias => {:detect, :siphon}
 end

All part of a LazyWeb query from Dan. And Mauricio’s code is only 17 lines long, very easy to peruse.

said on 01 Dec 2005 at 16:37

I like it, but I’ll make the same comment I was going to make on Dan’s journal (didn’t because Dan has comments off for non LJers):

I’d prefer a syntax that looks like this:


class MyList
  include Enumerable,
    :detect => :siphon,
    :sort_by => nil
end

In other words, suck the tail of the argument list into a hash. For each method that would be included, look in that hash. If the key is there but with a value of nil, exclude that method. If the key is there with a non-nil value, include it under the alias.

To me, such a syntax would be much cleaner and unified…

said on 01 Dec 2005 at 16:44

And here’s the implementation for the alternate syntax proposed above (shamelessly tweaked from Mauricio’s code, not tested):


class Class
  old_include = instance_method(:include)
  define_method(:include) do |*args|
    aliases = (Hash === args.last) ? args.pop : {}
    m = Module.new
    args.each{ |mod| m.module_eval{ include mod } }
    aliases.each_pair do |old, new|
      m.module_eval do
        alias_method(new, old) if new
        undef_method(old)
      end
    end
    old_include.bind(self).call(m)
  end
end

said on 01 Dec 2005 at 17:07

This notation isn’t so good either for Nitro and Og which uses dynamic modules. Hash parameters on #include can alter the behavior of the module via these include options.

Like I said on Mauricio site, Facets goes about it in slightly more Rubyeque way—a block is used with a mini-DSL to alter the new module. I named the method #integrate, instead of resusing #include, but it could be applied to #include just as well.

said on 01 Dec 2005 at 17:24

Maybe I should give and example here too:

class MyList
  itegrate Enumerable do
    remove :sort_by
    rename :detect, :siphon
  end
end

The internal part of the block is nothin special, its just be class_evaled on the new module—I’ve just created a few new useful method for this context. You could even define a new “one-time” method in it.

said on 01 Dec 2005 at 19:11

wow, that’s quite some pressure you’re putting on me, _why… 5+ worthy blog entries is a lot :)

said on 01 Dec 2005 at 19:54

Hm, I kinda like both lukfugl’s and trans’ ideas.

/me ponders

said on 01 Dec 2005 at 20:57

Haskell’s type classes are a really snazzy way of doing this… The syntax could be emulated by ruby syntax trivially I imagine.

said on 02 Dec 2005 at 00:58

But can this be used to implement perl6-style roles? Because that would be a neat thing to have in ruby.

said on 02 Dec 2005 at 01:03

Definitely. Both permutations above are excellent! Very well shown.

said on 02 Dec 2005 at 03:44

Tran’s permutation is great (an added plus: it integrates nicely with the dynamic mixins used in Nitro/Og ;-))

said on 02 Dec 2005 at 07:38

Can I just say, I don’t like the syntax the alias parameter is given in. {:detect, :siphon} prefers to be {:detect => :siphon}. 1) It looks less similar to alias detect siphon (which would imply the reverse alias) and 2) it is less easily confused with an array or block or anything other than the hash it is.

said on 02 Dec 2005 at 08:07

crzwdjk, if you happen to read my journal, my original idea was inspired by Curtis Poe’s Class::Trait Perl module.

While this solution wouldn’t match the trait spec completely (i.e. the Schaerli paper), it does deal with composition, which is the only part of traits I think we need for Ruby.

Also, see David Naseby’s blog entry (from a while back) on a theoretical implementation of traits here. It was that blog entry that I based the “interface” package on.

said on 02 Dec 2005 at 19:39

Can I ask for matz’ proposed syntax?

E= Readable+Writable+Enumerable - [:sort_by] 
E.rename {:detect=>siphon}
include E

I admit I love it when I see math operator overriding.

said on 02 Dec 2005 at 21:58

Traits Algebra? Hmm… then why not:

E=Readable+Writable+Enumerable-[:sort_by]-{:detect=>:siphon}

(Note: If the second plus didn’t show it’s _why’s fault!)

Still, that might be a little too terse, though it certainly seems very cool on first sight.

said on 02 Dec 2005 at 22:29

I love trans’ (non-algebraic) iteration

+1

said on 03 Dec 2005 at 00:10

riffraff, ick.

said on 03 Dec 2005 at 22:56

Hmm, the thing I’d like more than anything else would be the ability to only include those bits which I explicitly request.

I mean, okay, so you can explicitly something which would conflict today—but what if the module adds a conflicting method in the future?

That’s one thing that’s bothered me about Ruby for a long time. When everything’s the equivalent of a Java-ish star-import (e.g. import com.blah.*), you live dangerous-like.

said on 03 Dec 2005 at 23:42

So maybe

MappableAndFindable = Enumerable & [:map, :find]

But that is going to break things if the module’s methods depend on now-non-existing methods.

said on 04 Dec 2005 at 02:28

Module arithmetics implementation (was going to post here but it got a bit long)

said on 04 Dec 2005 at 08:05
Qux = (Foo & [:map, :read]) - { :map => :krunk }
Do people think that actually looks good? I think it looks ugly.
said on 04 Dec 2005 at 11:11

It’s like array set operation notation. Is the following more readable?

Qux = Foo.merge(Bar).
      only(:map, :read).
      rename(:map => :krunk)

Comments are closed for this entry.