Gangmax Blog

Ruby学习笔记第一篇: Ruby Sigils

在阅读Ruby代码时,经常会看到类似下面这样的代码:

1
2
3
4
5
def some_method(*a, &b)
...
end
call_some_method(:name=>"test", :type=>"some")
a.do_something if a.respond_to?(:do_something)

这些变量前面的”*”, “&”, “:”是什么意思?我这两天花了点时间学习,归纳为以下代码:

代码一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
$ irb
1.9.2p290 :001 > def t(a={}, *b, &c)
1.9.2p290 :002?> puts a.inspect if a
1.9.2p290 :003?> puts b.inspect if b
1.9.2p290 :004?> puts c.inspect if c
1.9.2p290 :005?> c.call(a) if c
1.9.2p290 :006?> end
=> nil
1.9.2p290 :007 > t(5, :name=>"test", :type=>"method")
5
[{:name=>"test", :type=>"method"}]
=> nil
1.9.2p290 :008 > t(5)
5
[]
=> nil
1.9.2p290 :009 > t(:name=>"test", :type=>"method")
{:name=>"test", :type=>"method"}
[]
=> nil
1.9.2p290 :010 > t(:name=>"test", :type=>"method"){|hash| hash.each{|k,v| puts "#{k}=#{v}"} if hash && hash.respond_to?(:each)}
{:name=>"test", :type=>"method"}
[]
#<Proc:0x9e30518@(irb):10>
name=test
type=method
=> {:name=>"test", :type=>"method"}
1.9.2p290 :011 > t(5, :name=>"test", :type=>"method"){|hash| hash.each{|k,v| puts "#{k}=#{v}"} if hash && hash.respond_to?(:each)}
5
[{:name=>"test", :type=>"method"}]
#<Proc:0x9e14c8c@(irb):11>
=> nil
1.9.2p290 :012 > t(5){|hash| hash.each{|k,v| puts "#{k}=#{v}"} if hash && hash.respond_to?(:each)}
5
[]
#<Proc:0x9df321c@(irb):12>
=> nil
1.9.2p290 :013 > t(:name=>"test", :type=>"method", 5){|hash| hash.each{|k,v| puts "#{k}=#{v}"} if hash && hash.respond_to?(:each)}
SyntaxError: (irb):13: syntax error, unexpected ')', expecting tASSOC
...me=>"test", :type=>"method", 5){|hash| hash.each{|k,v| puts ...
... ^
(irb):13: syntax error, unexpected '}', expecting $end
from /home/user/.rvm/rubies/ruby-1.9.2-p290/bin/irb:16:in `<main>'
1.9.2p290 :014 >

代码二

1
2
3
4
5
6
7
8
9
10
11
12
$ irb
1.9.2p290 :001 > def x(*a, b, &c)
1.9.2p290 :002?> c.call(a,b) if c
1.9.2p290 :003?> end
=> nil
1.9.2p290 :004 > x(5, 6, :name=>"t", :type=>"x") {|a,b| puts a.inspect + "," + b.inspect}
[5, 6],{:name=>"t", :type=>"x"}
=> nil
1.9.2p290 :005 > x(5, 6) {|a,b| puts a.inspect + "," + b.inspect}
[5],6
=> nil
1.9.2p290 :006 >

从以上代码可以了解到以下概念:

Argument Prefix “*”

以”*“开头的方法参数,表示将调用方所有传递给该方法的参数(注:其实是排除了其它显式被要求的参数的所有参数,在以上示例代码中可以看出这一点)传递到这个以”*abc”命名的Array对象中。

–翻译自 Programming Ruby 2nd Edition “Blocks for Transactions”一节。

Argument Prefix “&”

如果一个Ruby方法的最后一个参数以”&”开头,Ruby会在该方法被调用时试图找到一个block。如果找到,该block会被转换成一个Proc类的对象。… 更有意思的是,你得到的这个Proc对象,不仅包含block中定义的代码,还包含定义该block处的所有上下文信息,比如self引用、方法、变量以及常量。Ruby的这一神奇之处在于:在该block被实际调用的时候,即使定义该block的原始范围信息看上去已经消失了,在该block的范围之内,那些原始的上下文信息还仍然可用。在其它语言中,这种功能叫做闭包(closure)。

–翻译自 Programming Ruby 2nd Edition “Blocks Can Be Closures”一节。

Symbol “:”

Symbol是Ruby语言内置的一个class。我的理解是,它主要被用来作为Ruby编程中的“命名对象”(naming object)。Symbol对象被用于表示Ruby的符号表。运行以下代码会让你感到有点惊讶。

1
Symbol.all_symbols.each {|s| puts s}

那么 Symbol 对我们有什么用呢?当然也是内存。使用 String 的开销太大了,因为每一个String 都是一个对象。想想前边的例子,一个字符串每出现一次 Ruby 就会创建一个 String 对象。

通常来讲,当你面临 String 还是 Symbol 的选择时,可以参考以下标准:

  1. 如果使用字符串的内容,这个内容可能会变化,使用String;
  2. 如果使用固定的名字或者说是标识符,使用 Symbol。

那么什么时候我们会用到名字呢?很多时候都会,比如枚举值、hash keys、方法的参数)等等。

哈希表是Symbol应用最为广泛的地方。ROR 中就大量地运用这种方式,也许你已经看到了,到处都是Symbol和hash。使用hash参数的方法可以如下定义,前半部分为固定参数,后面为可变参数,或者干脆全采用哈希参数:

1
2
3
4
5
6
7
def my_method(para1, para2, options={})
#your code
end

def my_method(options={})
#your code
end

如果你希望设定一些默认参数,并允许调用者更改这些参数,可以使用哈希对象的 merge! 方法。比如”hsh.merge!(other_hash)”。该方法将other_hash里内容加到hsh中,如果other_hash与hsh有重复的key,则key在other_hash中的value覆盖hsh中对应key的value。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Test
def my_method(opts={})
default_opts={:arg1 => 10, :arg2 => "abc"}
default_opts.merge!(opts)
default_opts.each{|key,value| puts "#{key} is #{value}"}
end
end

t = Test.new
t.my_method :arg1=>5, :arg3=>"def"

# The result is:
arg1 is 5
arg2 is abc
arg3 is def
# Note: when *puts "#{key}"*, the output key string has no leading ":", I guess that because the "to_s" method of Symbol class doesn't show the ":", and you can verify this by running *:abc.to_s* in irb.

–摘自这里

我怀疑Ruby的这种”argument prefix”的变量定义风格来自于Perl,第一眼看上去感到很怪异,但不得不承认,这种风格使用起来很方便且功能很强大。

除了argument prefix,让我感到惊奇的是Ruby以下的访问控制实现:

在Ruby中直接定义的方法(不被包含在任何class/module中),看起来不属于任何类,但实际上Ruby将其分给Object类,也就是所有其它类的父类。因此,所有对象现在都可以使用这一方法。这本应是正确的,但有个小陷阱:它是所有类的私有(private)方法.我们将在下面讨论这是什么意思,但一个结果是它只能以函数的风格调用,像这样:

1
2
3
4
5
6
7
8
ruby> class Foo
def fourth_power_of(x)
square(x) * square(x)
end
end
nil
ruby> Foo.new.fourth_power_of 10
10000

我们不允许向一个对象明确地运用这一方法:

1
2
ruby> "fish".square(5)
ERR: (eval):1: private method `square' called for "fish":String

这一聪明的做法使得Ruby可以像在传统语言中那样运用函数的同时,保持了Ruby 的纯OO性质(函数仍是对象方法,但接受者隐式的为self)。

以上引用文字来自这里

参考了镐头书以及这里这里,还有专门介绍Ruby的symbol的两篇文章[1][2]

Added@20120614:

今天在我看到”通天塔导游:各种编程语言优缺点“这篇文章时,才发现在Perl语言中,变量名称前面的”$”这种符号有一个专门的术语:sigil。所以很自然地,本文讨论的内容就是”ruby sigils”。

Comments