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_
asdef
A,:g
asA#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() { ... };
A*.*:g as A#g
ReplyDeleteOh, I did a typo. Thx!
ReplyDelete