hoodwink.d enhanced
RSS
2.0
XHTML
1.0

RedHanded

EigenCharges #

by why in bits

A new sleight of syntax for ya:

 class RubyTalk < Filter
   + to("ruby-talk@ruby-lang.org")
   + cc("ruby-talk@ruby-lang.org")
   - from("common.troll@gifted.net")
 end

 class SelfBlog < Filter
   + to("selfblog@hobix.com")
   + subject("secretpassword")
   def process(msg)
     Blog.add_post(msg)
   end
 end

Young ones, see if you can figure out how it’s done before I hand you the actual raw sprinkles of meta magic dust. Specifically: (1) where are the rules getting stored? and (2) how are the operators actually implemented? Just have a think before the spoilers.

First, the Code

 class Filter
   class Rule
     attr_accessor :field, :terms, :action
     def initialize(f, *a)
       @field = f
       @terms = a
     end
     def -@; @action = :reject end
     def +@; @action = :accept end
   end

   class << self
     attr_accessor :rules
     [:from, :to, :cc, :subject].each do |m|
       define_method(m) do |*a|
         r = Rule.new(m, *a)
         (@rules ||= []) << r
         r
       end
     end
   end
 end

To view the rules for mail going into the RubyTalk mailbox:

 RubyTalk.rules.each do |r|
     puts "[#{r.action}] if #{r.field} matches `#{r.terms}'" 
 end

And, the Answers

1. Where are the rules getting stored? The rules are stored in instance variables inside each class. So, not in a RubyTalk object. In the actual RubyTalk class. Right next to the methods.

2. How are the operators actually implemented? By now, you probably can see just fine. Unary operators on the Rule class. The to and from class methods create Rule classes, which then get charged positively and negatively by the operator.

said on 04 May 2006 at 12:03

That’s beautiful.

You know, it’s a bit embarassing, but before this I had never learnt what method names went with the unary operators. I shall have to file this away…

said on 04 May 2006 at 12:17

I get this, except for:

def -@; @action = :reject end

What does the @ do? As opposed to have just “def -;”

said on 04 May 2006 at 12:27

The unary ops are -@, +@, ~@, !@

said on 04 May 2006 at 12:29

Jon: I believe -@ +@ Are the unary operator method names

said on 04 May 2006 at 12:39

Ah, I see. Thanks.

said on 04 May 2006 at 12:41

why: awesome, thanks~

said on 04 May 2006 at 12:59

It appears that !@ is not definable?

said on 04 May 2006 at 13:41

wait wait..i thought i had it but i lost it. The unary + and – methods are in the Rule class, right? So how is it possible that you can call them from Filter? (Like in your example at the top.)

said on 04 May 2006 at 13:45

teebag: The unary operators are called on the results of from(), to(), etc. which are Rule objects.

said on 04 May 2006 at 13:49

Ezra: ah, good catch. Yes, everything to do with truth in Ruby is hard-coded.

said on 04 May 2006 at 13:50

(bit-twiddling is lies. all of it.)

said on 04 May 2006 at 14:01

Bonus Challenge

Get the RubyTalk one to work like this:

 class RubyTalk < Filter
   + to | cc("ruby-talk@ruby-lang.org")
 end
said on 04 May 2006 at 14:13

oooooooooooooooooo. gotit.

said on 04 May 2006 at 14:40

!@ is not a unary operator. Unfortunately it is pure syntax.

said on 04 May 2006 at 14:43

excellent, without eval!

problem is: it produces warnings (useless use of +@ in void context.)

ko1 made a lang with this +@ trick: http://flgr.0×42.net/deobfu/1-negaposi.html

said on 04 May 2006 at 14:46

I’m sorry. I got that from parse.y, which lists the unary operators in op_tbl. Which means:

 >> "!(unary)".intern
 => :!
 >> "!@".intern
 => :!

But which doesn’t actually work:

 >> true.send("!(unary)")
 NoMethodError: undefined method `!' for true
        from (irb):2:in `send'
        from (irb):2
said on 04 May 2006 at 14:51

Ooh, nice ;-)

said on 04 May 2006 at 14:56

Did I mention that that x-thing in Textile was a real bad idea?

http://flgr.0×42.net/deobfu/1-negaposi.html

said on 04 May 2006 at 14:58

I guess unary ! is parsed and converted to some rb_negate method. Same to != and !~.

said on 04 May 2006 at 15:01

NegaPosi

sorry for the spam :( I should really use the Preview. And the Textile Docu.

said on 04 May 2006 at 15:12
said on 04 May 2006 at 15:13

Wait a minute… >_>

why, are you telling us that x.intern.to_s == x isn’t an invariant for non-empty strings?

That sucks—I think almost everybody was under the erroneous impression that it was. Speaking for myself, I know I am going to have to rewrite some code not to use symbols now…

said on 04 May 2006 at 15:24

It’s true. In a few cases.

 >> "+(binary)".intern
 => :+
 >> "-(binary)".intern
 => :-
 >> "+(unary)".intern
 => :+@
 >> "-(unary)".intern
 => :-@
 >> "!(unary)".intern
 => :"!" 
 >> "~(unary)".intern
 => :~
 >> "!@".intern
 => :"!" 
 >> "~@".intern
 => :~
said on 04 May 2006 at 15:40

I guess that’s okay for many things. My little lisp interpreter’s going to suffer though.

said on 04 May 2006 at 15:44

(I wouldn’t use it personally, but according to the R5RS grammar ! and !@ are both valid identifiers.)

said on 04 May 2006 at 16:15

huh. I knew there were a couple things like that, but I didn’t realize how bad it was. I think they should be invariant as described above, and the parser/eval should be responsible for dispatching correctly, not the other way around.

said on 04 May 2006 at 16:46

As I said in my unposted wink, I smell something fishy. Like a Ruby spam filter. Or something of the sort. This is too EigenSpiffy to only be something to throw arround.

said on 04 May 2006 at 17:02

Always wondered about the unary operator def syntax. Too bad about that !@ not being definable, though. Was this a yacc limitation or something? Could it be changed in 1.9?

said on 04 May 2006 at 21:28

Huh. I didn’t know you could use instance variables in a class directly via a meta-class. That’s actually kinda frightening.

said on 04 May 2006 at 21:54

Wait, is the | operator supposed to make it so that the latter rule’s terms are copied into the former rule or what?

Yah know, I mean, the syntax is cool and all, but, maybe it’s TOO cool, for school, you know?

As a DSL , yeah, sure, but it’s… hrmm.

Well, in any case, I expect _why’s next feat to be shoe-horning the Klingon language into a DSL for putting lids on bacon grease tubs.

said on 04 May 2006 at 22:43

Oh, come on, Danno. Just do the exercise. You’ll learn something neat. The pipe is an “or”.

said on 04 May 2006 at 23:03

pHiL: It’s got nothing to do with yacc at all.

Ruby’s boolean operations are all hard-coded for efficiency, not implemented via methods.

said on 05 May 2006 at 02:10

unariffic!

said on 05 May 2006 at 06:23
>> :'!(unary)' # => :"!"

caveat emptor!

said on 05 May 2006 at 07:24

That is one of the simplest, and most beautiful meta-programming hacks I’ve seen in quite a while.

said on 05 May 2006 at 10:06

Matz has just verified that the !@ operator isn’t callable and will likely be removed.

said on 05 May 2006 at 11:04

So here’s a start. If you want to be able to chain a bunch of |’s together, then Rule might need to keep a list of siblings with which it would like to share it’s terms.


class Filter
  class Rule
    def -@; @action = :reject; self end
    def +@; @action = :accept; self end
    def |(other_rule)
      both_terms = @terms
      both_terms += other_rule.terms
      other_rule.terms = both_terms
      @terms = both_terms
      self
    end
  end
end
said on 05 May 2006 at 11:51

why: Can you also ping him about whether we can eventually avoid having different strings intern to the same symbol?

said on 07 May 2006 at 20:20

So, can anyone a bit more familiar with Ruby internals tell me the reason strings like ”” are converted to : ?

said on 07 May 2006 at 22:24

Okay, I just can’t get this right. I find any way to make plus signs display at all, unless it’s only broken in the ajax-y preview… What I meant to ask is why are strings like "+(unary)" converted to :+@ (COLON PLUS AT -SIGN)

said on 09 May 2006 at 23:14

We want

- to | cc("ruby-talk@ruby-lang.org")

to be a shortcut for

- to("ruby-talk@ruby-lang.org")
- cc("ruby-talk@ruby-lang.org")

(I’m rejecting ruby-talk mail because Textile makes it hard to use plus.)

Note - to | cc(a) means (-to) | cc(a). That means:
  1. the | method is applied last
  2. the left operand has the action to apply (actually, IS the action unless we change Rule#-@ to return self)
  3. the right operand has the terms
 class Filter::Rule
   def -@; @action = :reject; self end
   def +@; @action = :accept; self end
   def |(other)
     @terms |= other.terms
     other.action = action
     [self, other]
   end
 end
said on 12 May 2006 at 10:32

Though I wouldn’t use it since it’s ugly, here’s my solution to the challenge :

 class Filter
   class Rule
     attr_accessor :field, :terms, :action
     def initialize(f, *a)
       @field = f
       @terms = a
     end
     def -@; @action = :reject; self end
     def +@; @action = :accept; self end
   end

   class RulePlaceholder
     attr_accessor :fields
     def initialize(f, filter)
       @filter = filter
       @fields = [f]
     end
     def |(other)
       if other.kind_of? RulePlaceholder
         @fields.concat other.fields
       elsif other.kind_of? Rule
         other.action = @action
         @filter.rules.concat @fields.map { |field| rule = Rule.new(field, *other.terms); rule.action = @action; rule }
       else
         :explode_penguin
       end
     end
     def -@; @action = :reject; self end
     def +@; @action = :accept; self end
   end

   class << self
     attr_accessor :rules
     [:from, :to, :cc, :subject].each do |m|
       define_method(m) do |*a|
         if a.empty?
           RulePlaceholder.new(m, self)
         else
           r = Rule.new(m, *a)
           (@rules ||= []) << r
           r
         end
       end
     end
   end
  end
 
said on 12 May 2006 at 12:09

I have seen the error of my ways and slapped it around a bit :

 class Filter
   class Rule
     attr_accessor :field, :terms, :action
     def initialize(f, *a)
       @field = f
       @terms = a unless a.empty?
     end
     def |(other)
       other.action ||= @action
       @terms ||= other.terms
     end
     def -@; @action = :reject; self end
     def +@; @action = :accept; self end
   end

   class << self
     attr_accessor :rules
     [:from, :to, :cc, :subject].each do |m|
       define_method(m) do |*a|
         r = Rule.new(m, *a)
         (@rules ||= []) << r
         r
       end
     end
   end
 end
said on 12 May 2006 at 12:12

I have seen the post right before mine and slapped me around a bit.

said on 14 May 2006 at 22:57

No, good work, C Erler. The thing is: you get it now. I’m sure Dave and Danno feel better now too. Accolades for all of you.

Comments are closed for this entry.