Blogged by Ujihisa. Standard methods of programming and thoughts including Clojure, Vim, LLVM, Haskell, Ruby and Mathematics written by a Japanese programmer. github/ujihisa

Monday, June 8, 2009

Defining Instance Method Easily and Viscerally

Motivation

There are two kind of methods in Ruby. One is a class method like File.open, the other is an instance method like Fixnum#to_s.

class A
  def self.f
    ...
  end

  def g
    ...
  end
end

The class A has a class method A.f and an instance method A#g. To call them, you just have to write A.f or A.new.g.

You can define a class method in another way:

# suppose class A is already exist.
def A.f
  ...
end

However, you cannot define an instance method in this way,

# suppose class A is already exist.
def A#g
  ...
end
# !!! This code defines Kernel#A instead of A#g

but in this way,

# suppose class A is already exist.
A.method(:define_method).call(:g) do
  ...
end

or

# suppose class A is already exist.
A.__send__(:define_method, :g) do
  ...
end

Now let's think about good notations of defining an instance method.

Case 1: Add Support Method

def def_(klass, name, &block)
  klass.method(:define_method).call(name, &block)
end

And then

def_ A,:g do
  ...
end

Now you can call A.new.g. The code is a little tricky. If your eyes are a little bad, you can see

  • def_ as def

  • A,:g as A#g

and can ignore do. You can imagine def_ A,:g do is def A#g!

There's only one bad news: when the method A#g has arguments, we have to write it as

def_ A,:g do |x|
  ...
end

That means your eyes must ignore more. We have to think more deeply.

Case 2: Fix parse.y

I succeeded to fix parse.y to be able to handle the notation.

First, I fixed the scanner to suspend to ignore the normal comment notation in a special case. For ease of fix, I compromised that if '#' appears before an alnum in the state EXPR_END, then it won't be considered as a comment in this time. Second, I fixed the parser so as to define an instance method.

Apply this patch to ruby-trunk revision 23651:

diff --git a/parse.y b/parse.y
index 636f51f..ee5fb22 100644
--- a/parse.y
+++ b/parse.y
@@ -2905,6 +2905,35 @@ primary                : literal
      in_def--;
      cur_mid = $<id>3;
        }
+                | k_def cname '#' {lex_state = EXPR_FNAME;} fname
+                    {
+                        $<id>$ = cur_mid;
+                        cur_mid = $5;
+                        in_def++;
+                    /*%%%*/
+                        local_push(0);
+                    /*%
+                    %*/
+                    }
+                  f_arglist
+                  bodystmt
+                  k_end
+                    {
+                    /*%%%*/
+                        NODE *body = remove_begin($8);
+                        reduce_nodes(&body);
+                        $$ = NEW_DEFN($5, $7, body, NOEX_PRIVATE);
+                        fixpos($$, $7);
+                        fixpos($$->nd_defn, $7);
+                        $$ = NEW_CLASS(NEW_COLON3($2), $$, 0);
+                        nd_set_line($$, $<num>6);
+                        local_pop();
+                    /*%
+                        $$ = dispatch4(defi, $2, $5, $7, $8);
+                    %*/
+                        in_def--;
+                        cur_mid = $<id>6;
+                    }
 k_def singleton dot_or_colon {lex_state = EXPR_FNAME;} fname
        {
      in_single++;
@@ -6373,6 +6402,9 @@ parser_yylex(struct parser_params *parser)
  goto retry;

       case '#':                /* it's a comment */
+        c = nextc();
+        pushback(c);
+        if(lex_state == EXPR_END && ISALNUM(c)) return '#';
  /* no magic_comment in shebang line */
  if (!parser_magic_comment(parser, lex_p, lex_pend - lex_p)) {
      if (comment_at_top(parser)) {

Now you can write:

def A#initialize(a)
  @a = a
end

def A#foo
  @a
end

p A.new(10).foo #=> 10

Wonderful!

Note

  • I ignored the concept of singleton methods or singleton classes on purpose to simplify the difference between class methods and instance methods

  • In JavaScript, it's easy to define an instance method.

    // class method A.f = function() { ... };
    // instance method A.prototype.g = function() { ... };
    

2 comments:

Followers