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

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

I get this, except for:

def -@; @action = :reject end

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

said on

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

said on

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

said on

Ah, I see. Thanks.

said on

why: awesome, thanks~

said on

It appears that !@ is not definable?

said on

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

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

said on

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

said on

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

said on

Bonus Challenge

Get the RubyTalk one to work like this:

 class RubyTalk < Filter
   + to | cc("ruby-talk@ruby-lang.org")
 end
said on

oooooooooooooooooo. gotit.

said on

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

said on

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

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

Ooh, nice ;-)

said on

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

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

said on

NegaPosi

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

said on
said on

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

It’s true. In a few cases.

 >> "+(binary)".intern
 => :+
 >> "-(binary)".intern
 => :-
 >> "+(unary)".intern
 => :+@
 >> "-(unary)".intern
 => :-@
 >> "!(unary)".intern
 => :"!" 
 >> "~(unary)".intern
 => :~
 >> "!@".intern
 => :"!" 
 >> "~@".intern
 => :~
said on

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

said on

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

said on

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

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

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

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

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

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

said on

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

unariffic!

said on
>> :'!(unary)' # => :"!"

caveat emptor!

said on

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

said on

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

said on

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

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

said on

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

said on

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

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

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

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

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

said on

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.