Project1

标题: Ruby/RGSS Tips 每日一更 [技术区的版聊帖?] [打印本页]

作者: DeathKing    时间: 2010-8-26 10:30
标题: Ruby/RGSS Tips 每日一更 [技术区的版聊帖?]


这个是一个偶然的想法,需要一定的试点。是这样的,这个帖子每天将会更新一些Ruby 或者 RGSS 的小技巧,这些技巧一般很简短,易于掌握。

细则如下:



注意!





作者: DeathKing    时间: 2010-8-26 10:31
标题: Sample - $game_switches和$game_variables
本帖最后由 DeathKing 于 2010-8-26 11:14 编辑

2010-08-26

$game_switches和$game_variables分别掌管游戏开关和游戏变量,他们都是数组(Array),访问使用的是[]方法,修改使用的是[]=方法。

对于$game_switches来说有效值为true(真,游戏中为开),false(假,游戏中为关);
对于$game_variables来说,理论有效值是Object(可以理解为所谓Ruby可识别的对象),但因为编辑器的限制,只能是数字(Numeric)或者字符串(String)。

将1号开关打开:$game_switches[1] = true
将5号变量归零:$game_variables[5] = 0
[line]1[/line]
内幕:
事实上,$game_switches和$game_variables并不是一个Array,其实变量@data才是一个数组。
Game_Switch和Game_Variable类定义了[]和[]=两个方法来从外部访问@data而已。
[line]1[/line]
补充:
紫苏:可以具象化——这两个东西实际上就是开关数组和变量数组的外壳,通过接口与外部通信
小幽的马甲:顺便,这俩的访问和修改不受数据库里上限影响——并且本来会返回nil时会返回0或false
作者: 紫苏    时间: 2010-8-26 10:55
有人会问:Ruby 没有析构方法?乍一看似乎这个真没有,其实这个可以有——
define_finalizer  ObjectSpace.define_finalizer( anObject, aProc=proc() )  

Adds aProc as a finalizer, to be called when anObject is about to be destroyed.  

ObjectSpace.define_finalizer 可以为某个对象添加一个类似 C++ 析构函数的最终化处理器,如果在 initialize (类似构造函数)中调用这个方法,就能为每个实例添加一个固定的析构方法
例:

  1. class MySprite < Sprite
  2.     def initialize(viewport = nil)
  3.         super(viewport)
  4.         ObjectSpace.define_finalizer(self, proc { self. dispose })
  5.     end
  6. end
复制代码
这样一来,当 MySprite 对象被回收时,精灵资源会自动释放
作者: zh99998    时间: 2010-8-27 08:39
标题: 2010-08-27 简略写法
p不是关键字,但是可以执行p 1正常输出1而不出错,为什么?
Ruby里调用方法时,如果不会产生歧义,则可以省略括号,使代码变得整洁美观
比如要输出一个跟RM里的用语【等级】同名的文件,可以简写成这样
  1. print IO.read Vocab.level
复制代码
这比完整写法
  1. print(IO.read(Vocab.level()))
复制代码
好看的多
但是一定要注意使用时的原则是不引起歧义,例如
  1. open File.expand_path "test.txt" , "w"
复制代码
就会出错,原因是参数"w"可能是expand_path的参数也可以是open的参数
所以必须加一层括号
  1. open File.expand_path("test.txt") , "w"
复制代码
Ruby中类似的简写除了括号之外,还有其他的,例如Hash
  1. p 1 => 2
复制代码
乍一看似乎是会Syntax,但是这一句是能执行的,输出{1 => 2}
这一特性在ruby的应用(窗口编程/网页)中使用得十分广泛
作者: DeathKing    时间: 2010-8-27 08:43
标题: 有关方法名的一些俗成约定
本帖最后由 DeathKing 于 2010-8-29 07:19 编辑

2010-08-28

昨天进行过一次讨论:http://rpg.blue/thread-154899-1-1.html
既然抢到了今天的帖子,那么就借花献佛。

Ruby中的方法名有一定的俗成的约定,下面列出了这些约定,不过,这不是强制性必须遵守的!

以 = 号结尾的方法通常是setter,会为同名实变量设定值;
以 ? 号结尾的方法返回的是一个所谓的布林值,也就是true或者false;
以 ! 号结尾的方法表示该方法具有破坏性,所以要小心操作。

一些例子:

关于破坏性与非破坏性的比较:

  1. ary = [1,3,5,7,9,2,4,6,8,10]

  2. ary.sort #=> [1,2,3,4,5,6,7,8,9,10]
  3. p ary    #=> [1,3,5,7,9,2,4,6,8,10]

  4. ary.sort! #=> [1,2,3,4,5,6,7,8,9,10]
  5. p ary     #=> [1,2,3,4,5,6,7,8,9,10]
复制代码

作者: IamI    时间: 2010-8-28 18:26
标题: 2010-8-29
本帖最后由 IamI 于 2010-8-29 12:59 编辑

Class、Module两个类是抽象的抽象。在Class中定义的实例方法是Class类的对象的方法,也就是普通的类的类方法。比如说:
  1. class Class
  2.   def hi
  3.     p "HI!"
  4.   end
  5. end

  6. class A
  7.   hi
  8. end
  9. A.new.hi
复制代码
不言自明。

Class和Moudle的类方法只能被显式的调用,诸如Class.new

另外,事实上,假设A是一个类,当你调用A.new方法的时候,操作如下所示:
A.new调用A.allocate => 生成实例
调用实例的initialize方法 => 根据伪多态完成初始化操作。

Object是一切的起源。它是超脱一切的存在(囧)。Object的include-in Kernel(你没看错,它是一个Mix-in)可以在任何位置被调用。也许你会问。既然Kernel是一个嵌入模块,那么为什么在没有类没有实例的方法也可以调用?因为直接p self可以(VX)看到顶级被调——Main :Object。是的,你逃不出Object的掌控。

如果你要给所有类定义一个方法,想在Class类eval?错了,instance_eval。具体请参考一开始的那段话。

OK,给下面两楼两个话题。Binding、$SAFE
作者: zh99998    时间: 2010-8-30 08:18
标题: 2010-8-30
重名陷阱

由于Ruby在语法上很人性化很自由,这也就带来了一些问题

例如
1.在方法调用中省略括号所引起的误会  注:标引用的部分引自Ruby手册
因为表示Range对象(或范围表达式)的.., ...的结合度较低(请参考操作符表达式),所以会引发下列错误
1..3.to_a
=> -:1: bad value for range (ArgumentError)
上面的代码会被解释成下面这个样子。
1..(3.to_a)
下面的代码不会得到预期的结果
p (1..3).to_a
因为它被解释成下面的代码。
(p (1..3)).to_a
p (true && true)    #=> true
p (true and true)   #=> parse error
p ((true and true)) #=> true
因为true and false被看作是语句,所以必须使用括号将其变为表达式的等价形式。同时,将它传给p的参数时还需要再加上一层括号才行。
p {1=>2}    #=> parse error (大括号被当作块)
p ({1=>2})  #=> {1=>2}
p (1=>2)    #=> {1=>2} (参数被当作哈希表)
p (1=>2, 3) #=> parse error    (只能把省略大括号的哈希表放在参数列表的最后)
p (0,1=>2)  #=> 0
                1=>2

2.运算符和方法的重名
a=1;
b = a+2;   # a 和 2 的和
b = a +2;  # 被解释成 a(+2)
b = a + 2; # a 和 2 的和

a = b = c = true
a?b:c       # 被解释成 a?(b(:c))
a ? b : c   # 条件操作符

3.变量和方法的重名
以小写字母开头的方法,可能与变量重名
  1. def a
  2.   1
  3. end
  4. p a #=>1
  5. a = 2
  6. p a #=> 2
  7. p a() #=> 1
复制代码
如果没有a这个变量,那么只p a就可以输出1
但是大写字母则不同,无论有没有同名常量,都必须加()
  1. def A
  2.   1
  3. end
  4. p A #=>出错
  5. A = 2
  6. p A #=>2
  7. p A() #=>1
复制代码
带等号方法
  1. def a=(val)
  2.   @a = val
  3. end
  4. a = 1
  5. p @a #=> nil
  6. self.a = 1
  7. p @a #=> 1
复制代码

作者: DeathKing    时间: 2010-8-31 00:02
标题: 2010-08-31
本帖最后由 DeathKing 于 2010-8-31 00:37 编辑

三元表达式、三元运算符(Ternary Operator

  平常接触的if控制结构都是这样的:
  1. if condition
  2.   exp1
  3. else
  4.   exp2
  5. end
复制代码
不过当exp只有一句的时候,这样看起来会十分的占地方,所以就有了下面这种简写为一排的方式:
  1. if condition then exp1 else exp2 end
复制代码
不过这样看起来也有很大一堆,还有没有更简单、优雅的写法呢?当然是有的,Ruby继承了C的三元运算符(Ternary Operator),上面的表达式会被替换成这样:
  1. condition ? exp1 : exp2
复制代码
实质上,首先会计算condition的值,为真的话就执行exp1,否则就就只exp2。不过要注意的是,三元运算符的优先级比=(赋值运算符)高,所以下面的做法是完全可行的:
  1. foo = true ? 1 : 0
  2. p foo #=> 1
复制代码
如果说你是Visual Basic程序员,你会发现,三元运算符就和IIf函数类似。
作者: 小幽的马甲    时间: 2010-9-1 08:49
本帖最后由 小幽的马甲 于 2010-9-1 08:50 编辑

2010-9-1

垃圾回收(Garbage Collection)

GC是个很有意思的东西。不管是用C的malloc和free还是Pascal的new和dispose管理内存都需要有一个释放的过程,有的时候会很麻烦,GC就是把这个过程智能化了,可以把不用的对象(垃圾)自动释放掉。
RM里的GC模块只有三个方法:GC.enable(打开GC)、GC.disable(关闭GC)、GC.start(初始化GC),很好理解。
为了测试GC的效果,我们可以在事件里调用
  1. a = Window_Help.new
  2. a = 1
复制代码
执行后可以看到,地图上仍会留一个窗口,但是约一分钟后就会自动消失。
如果后面加句GC.start则会立刻消失。
当然,平时写脚本是绝对不要依赖GC,该释放的对象还是要释放。
默认脚本中只有在Cache.clear里用到了GC,因为那时候缓存的哈希表被清空,但对象并没有被释放,所以需要调用GC。
作者: 紫苏    时间: 2010-9-2 10:06
2010-09-02 Proc.new 和 lambda
Proc 是 procedure 的简称,意味着一个过程,或者例程。我们基本上可以把 Ruby 的 Proc 对象看作是 Ruby 的匿名函数,但在 Ruby 1.8 中,通过不同方法获取的 Proc 对象有微妙的区别:
  1. def foo
  2.   local = 'This is a local variable'
  3.   $proc = Proc.new { return }
  4.   $proc.call # OK
  5.   p '这里永远不会执行'
  6. end

  7. foo
  8. $proc.call # error
复制代码
在 foo 方法内部,$proc 执行后,直接结束了函数的运行,而在方法外部调用 $proc 则会产生非局部跳转的异常,这里的意思是告诉你不能在顶层 return。从这个现象可以得出结论:Proc.new 会绑定创建时的上下文,而在这个 Proc 对象内 return 则是与该上下文绑定的。比如,上面的代码中,$proc 创建时的上下文是 foo 方法的局部上下文,在方法内部调用 $proc 时 foo 的局部上下文还存在(方法还没有返回),所以没有问题;在方法外部调用 $proc 时 foo 的局部上下文已不复存在(方法已经返回),所以发生了异常
我们再来看看闭包的定义:
在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

然而 Proc.new 产生的 Proc 对象并没有真正的自由,它与创建时的上下文绑定在了一起,并不是一个独立的匿名函数个体(因为不能自由地 return,且成功 return 后会让外部的函数返回),所以它并不算是真正意义上的闭包
可喜的是,刁钻古怪的 Ruby 还是用一种隐晦的方式提供给了我们真实的闭包。把上面的代码中的 Proc.new 替换为 lambda 后,就能发现没有错误了。lambda 是 Kernel 里的方法,这个名称来源是 λ-calculus 中的 λ 符号,意味着一种匿名函数演算法,由 Scheme 语言最早支持,后来被各种语言继承。lambda 产生的 Proc 对象,其内部 return 是从当前的 Proc 对象,即当前的闭包返回,所以 lambda 产生的 Proc 对象才是 Ruby 真正的闭包
这里还有最后一个问题,就是 Ruby 1.8 对 proc 和 lambda 的定义产生了混淆。proc 也是 Kernel 里的一个方法,其文档中表示 Kernel#proc 等价于 Proc.new,但当我们实际把上面代码中的 Proc.new 替换为 proc 时,会发现没有异常。实际上在 Ruby 1.8 中,Kernel#proc 是等价于 Kernel#lambda 的,也就是说 Kernel#proc 也是真实的闭包。然而从命名上来看,proc 和 Proc.new 不同而合 lambda 相同,是一个很明显的设计失误,所以在 1.9 中,Ruby 巧妙地让 Proc.new 等价于了 Kernel#proc
作者: 紫苏    时间: 2010-9-3 12:46
2010-09-03 冗余代码

所谓冗余,是指没有意义、永远不会被执行的代码,或多次运算且结果相同的不必要代码。前两者很容易理解,这里我们主要说一下“多次运算的结果相同的不必要代码”。这种冗余不但可能导致代码可读性的降低(多次重复编写一大段代码),还可能降低代码运行的效率(代码容量的增加导致的程序装载效率以及多次进行结果必然相同的表达式运算)。在软件工程的哲学中有一个广为人知的原则,叫做 DRY 原则,全称是 Do not Repeat Yourself,是避免冗余的基本原则,意思是不要让相同的代码出现大于一次。例如:

  1. a = 0
  2. b = 32
  3. for i in 0..100
  4.   a += i
  5. end
  6. for i in 5..323
  7.   b += i
  8. end
  9. p a + b
复制代码
我们这里有两个整数 a 和 b,分别初始为 0 和 32,我们想让它们的值递增上一系列连续的整数,于是写了两个框架基本上一样的循环。其中第二个循环就是冗余代码,我们完全可以编写一个函数(子例程)来完成这一个运算:
  1. def fn(a, b)
  2.   ret = 0
  3.   for i in a ..b
  4.     ret += i
  5.   end
  6.   return ret
  7. end

  8. a = 0
  9. b = 32
  10. a += fn(0, 100)
  11. b += fn(5, 323)
  12. p a + b
复制代码
这是提高了代码可读性的冗余消除。另一种情况:
  1. def fn(a, b)
  2.   $global = a*b
  3.   return a*b
  4. end
复制代码
这里,return 语句则是一句冗余代码,因为它在第一次计算 a*b 且结果保存在了 $global 后第二次计算了 a*b,而这是不必要的,因为 $global 这个变量是永远可见的,可以直接引用它并返回:

  1. def fn(a, b)
  2.   $global = a*b
  3.   return $global
  4. end
复制代码
然而这还属于无伤大雅的冗余,毕竟如今的 CPU 条件下,可以在极其高效的对数时间内完成乘法运算;糟糕的是出现需要花费时间执行的代码出现了冗余:
  1. arr = [1, 3, 52, -2, 0, -234, 33, 6, 76, 44, 3, -2, 5]
  2. res = 0
  3. res += 6 if arr.include?(6)
  4. res += arr.sort[2]
  5. if arr.include?(6)
  6.   res += arr.sort[3] << 3
  7.   p "#{res}foo"
  8. end
  9. p "#{res}bar"
复制代码
这里,arr.include? 和 arr.sort 都出现了两次,而由于期间 arr 没有改变,故这两者的第二次出现都是冗余代码;恰好这两者都是耗时间的操作——include? 需要线性时间内完成,而 sort 需要线性对数时间内完成。我们将冗余消除后,理论上运行效率会提升将近一倍:

  1. arr = [1, 3, 52, -2, 0, -234, 33, 6, 76, 44, 3, -2, 5]
  2. res = 0
  3. has6 = arr.include?(6)
  4. sorted_arr = arr.sort
  5. res += 6 if has6
  6. res += sorted_arr[2]
  7. if has6
  8.   res += sorted_arr[3] << 3
  9.   p "#{res}foo"
  10. end
  11. p "#{res}bar"
复制代码
然而你也不能只留意在源脚本中实际出现了多次的代码冗余,有时在某些特殊控制结构里,比如循环,即时表达式在字面上仅出现了一次,实则也可能是一种冗余:
  1. arr = [1, 3, 52, -2, 0, -234, 33, 6, 76, 44, 3, -2, 5]
  2. for i in 0..100
  3.   sorted_arr = arr.sort
  4.   p sorted_arr[rand(sorted_arr.length)]
  5. end
复制代码
这里,arr.sort 会被循环执行,但实际上进行排序的数组没有改变,每次排序的结果都是相同的,所以我们可以把这一行拿到循环外面去,避免了冗余:
  1. sorted_arr = arr.sort
  2. for i in 0..100
  3.   p sorted_arr[rand(sorted_arr.length)]
  4. end
复制代码
假设数组长度是 n,那理论上这里的冗余避免后效率是避免前的将近 100 倍。
DRY 原则也不是绝对要遵守的,要看情况而定——有时出现的冗余,比如多次通过 [] 数组下标访问数组元素,影响并不大,因为它们并不耗费多少时间,为了它们去分配一个临时变量来保存可能并不值得。在编译性语言以及有即时编译机制的解释性语言中,聪明的编译器和解释器会自动进行各种优化,如公共子表达式消除、死代码消除、常数合并,能杜绝大部分冗余,但 RM 所使用的 Ruby 1.8 MRI 解释器直接解释源代码,没有任何优化,所以是需要用户来考虑效率的
作者: 紫苏    时间: 2010-9-4 22:45
2010-09-04 不常见的字面值及 Shell 命令表示法
Ruby 支持的字面值和 Shell 命令表示法很多样化,我们最常用的是:
1、双引号字符串,如 "hello\nworld",支持转义字符和内嵌表达式
2、单引号字符串,如 'hello world',原始字符串数据,不支持转义字符和内嵌表达式(在不需要这两者的时候尽量用单引号提高效率)
3、正斜杠正则表达式,如 /regex/i
4、反引号 Shell 命令,如 `pause`
除此之外,Ruby 还支持所有 Unix Shell 以及大部分脚本语言都支持的 Here document,能够方便地进行多行的字符串输入:

  1. str = <<DELIMETER
  2. 桑之未落
  3. 其叶沃若
  4. 桑之落矣
  5. 其黄而陨
  6. DELIMETER
  7. p str
复制代码
如果你用过字幕脚本,你就会觉得这段脚本很熟悉,因为字幕脚本用的也是 here document。DELIMITER 是自定义的分隔符,在两个分隔符(这里是两个 DELIMITER)之间的内容会被认为是字符串输入。不过这样写的 here document 不能进行缩进,强行缩进的话,最后的 DELIMETER 处会发生语法错误。在 << 后加上短横线 - 就可以解决:
  1. if true
  2.   str = <<-DELIMETER
  3.   桑之未落
  4.   其叶沃若
  5.   桑之落矣
  6.   其黄而陨
  7.   DELIMETER
  8.   p str
  9. end
复制代码
Ruby 还有所谓的常规定界输入(General delimited input),使得字符串、正则表达式、字元数组和 Shell 命令有了额外的表示法:
%q{这相当于一个单引号字符串}
%Q{这相当于一个双引号字符串}
%{这相当于一个单引号字符串}
%w{这 是 一 个 字 元 数 组 , 字 元 以 空 白 符 分 隔}
%r{[0-9]+} # 正则表达式
%x{mspaint} # Shell 命令
其中 `{' 和 `}' 是用来表示该输入的开始和结束的,也可以用其它分隔符表示,如
%#这是一个双引号字符串# # 这前面可不是注释哦,虽然 RGSS 的编辑器将它当做注释高亮显示了,或者两两对应的字符, `(' 和 `)',`[' 和 `]',以及 `<' 和 `>'
常规定界输入也支持多行输入,%q 和 % 使用起来比 here document 还方便!
作者: 紫苏    时间: 2010-9-5 11:01
本帖最后由 紫苏 于 2010-9-5 11:08 编辑

2010-09-05 绑定 Binding

在计算机科学中,“绑定”一词是指一个更复杂、更大型的物件的引用的创建。例如当我们编写了一个函数,这个函数名就绑定了该函数本体,我们可以通过函数名来引用并调用该函数,这被称为名称绑定;又如当 Ruby 通过 API 去调用了 C 语言写的库函数时,这就是一个语言绑定;再如面向对象语言中的方法调度 obj.method,这也是一个名称绑定,它会根据接收者 obj 具体的对象类型来确定应该引用哪个对象类型的 method 方法,而如果 obj 在编译时就能确定,那便可称之为静态绑定(早绑定),早期的静态类型语言(如 C)使用的是早绑定;如果 obj 在运行时才能确定,那便可称为动态绑定(迟绑定),动态类型语言(如 Ruby)使用的是迟绑定,而有些语言则同时支持早绑定和迟绑定,如 C++ 的虚函数使用迟绑定,普通函数则使用早绑定。
在 Ruby 中,Kernel 有一个方法 binding,它返回一个 Binding 类型的对象。这个 Binding 对象就是我们这里说的绑定,它封装了当前执行上下文中的所有绑定(变量、方法、语句块、self 的名称绑定),而这些绑定直接决定了面向对象语言中的执行环境。比如,当我们调用 p 时,实际上是进行了 self 和 p 的绑定,而 p 具体是哪个方法,是由 self 的类型来决定的,如果我们在顶层,而 Kernel#p 又没有被重写,那 p 就是一个用来显示对象细节的方法。可以说有了一个绑定的列表,我们就有了一个完整的面向对象上下文的拷贝,就好比上帝在 12 分 37 秒复制了一份世界,而这个世界与原本世界的环境一模一样,既有这朵花,又有那株草。Ruby 的 Binding 对象的概念和 Continuation 有共通之处,但 Continuation 主要用于实际堆、栈内存的环境跳转,而 Binding 则比较高层。
这个 Binding 对象有什么用?主要是用于 eval 这个函数。eval 的第一个参数是需要 eval 的一段脚本字符串,而第二个可选参数则接受一个 Binding 对象。当指定了 Binding 时,eval 会在传递给它的 Binding 所封装的执行环境里执行脚本,否则是在调用者的执行环境里执行。我们可以通过这个机制来进行一些不同上下文之间的通信,或者是在一个上下文即将被销毁之前保存该上下文环境以留他用,如:
  1. def foo
  2.   bar = 'baz'
  3.   return binding
  4. end

  5. eval('p bar', foo)
复制代码
这里我们通过 foo 返回的 Binding 获取到了局部上下文销毁前的局部变量 bar 的值,而在不使用 binding 的情况下,局部变量 bar 在 foo 外层是不可见的。
最后,Ruby 有一个预定义的常量:TOPLEVEL_BINDING,它指向一个封装了顶层绑定的对象,通过它我们可以在其它上下文中通过 eval 在顶层上下文环境中执行脚本。
作者: Phil    时间: 2010-9-5 13:47
提示: 作者被禁止或删除 内容自动屏蔽
作者: 紫苏    时间: 2010-9-7 12:28
2010-09-07 Continuation
Ruby 1.8 里有一个内置类 Continuation,中文直翻是“延续”,这是计算机科学中一个比较底层的概念,它表示进程执行的某一特定时刻的计算过程的实例。通俗的讲,它就是 Ruby 中比 Binding 更加底层的一个能保存当前进程执行环境的物件,就好比游戏里,我们通过存档就能在环境改变之后重新读档,从档案记录的环境,记录的那一刻开始。“延续”一词,即是暗示了从记录的计算过程中的一个点继续执行之意。
Ruby 程序的执行使用了相比于堆要更高效的栈结构来进行函数调用和局部变量的保存,而 Continuation 正是保存了进程运行在某一时刻时的栈内存状态,之后用户可以手动地去调用这个 Continuation 对象,让程序从这一个预先保存的记录点运行。这相当于不少语言中支持的 goto 语句跳转的一个超级版,因为 goto 只能进行局部跳转,而 Continuation 能上天入地,翻江倒海,一个筋斗十万八千里(实际上在 C 标准库中,Continuation 的功能是等同于 setjmp 和 longjmp 这两个函数所提供的功能的)。
有什么用?RM 有一个快捷键为 F12 的重置游戏的功能,当触发了这个命令时,RM 会让解释器从头开始重新执行脚本编辑器里的脚本,但并没有初始化符号表、GC 和堆结构。这使得原有的全局变量、常量、模块、类得以保存,但也可能会让我们与我们的老朋友——stack level too deep——再度重逢。由于脚本中大量使用 alias 别名方法,当 F12 按下后,没有进行判断的 alias 语句再次执行,就会导致两次同名一个方法后产生的经典 BUG(关于这个问题,这里不再赘述,论坛上已经有太多相关的帖子,请自行搜索 stack level too deep)。这个问题可以通过 method_defined? 等方法判断将要 alias 的方法是否已经定义来解决(alias 一次后方法就已定义了,第二次不会再定义),但并不是所有脚本编写者都有这个习惯。这里我们介绍一种使用 Continuation 的通用解法:
在 Main 脚本里的第一行插入一句:
  1. callcc{ |$__f12_no_reeval| }
复制代码
这一句脚本让 RM 在执行到 Main 脚本时将一个保存了当前执行环境的 Continuation 对象赋给了 $__f12_no_reeval 这个全局变量;接着在脚本编辑器开头插入:
  1. $__f12_no_reeval and $__f12_no_reeval.call
复制代码
这句是先判断 $__f12_no_reeval 是否已初始化(即已经执行到 Main 脚本一次以上),如果是则调用这个 Continuation,让程序直接跳转到 Main 脚本开头的环境继续执行,避免了从开头到 Main 脚本之间的所有脚本的重新执行。由于产生冲突的、包含 alias 的脚本在这些脚本之中,保证只执行它们一次就从根本杜绝了这个问题的滋生。

关于 Continuation 的额外知识:
在 Ruby 1.8 中,Continuation 的实现模型是与线程的实现模型一致的——线程调度器在进行绿色线程切换的时候,会进行类似 Continuation 的环境跳转。在 Ruby 1.9 中,由于 YARV 虚拟机采用的是本地线程,每个线程拥有自己的私有栈,他们之间就产生了区别。有趣的是,在 Ruby 1.9 中又引入了一个新的和 Continuation 类似的概念,那就是我们的 Fiber(纤程),一种轻量级的线程,它基本可以被看作是 Continuation 的替代品,同时由于效率比 Continuation 高(Continuation 保存栈内存状态的过程开销是巨大的),Ruby 社区就是否保留 Continuation 进行了激烈的辩论,就 1.9 而言,Continuation 仍然被保留下来了。下一次我们就来看看这个 Ruby 1.9 中的新东西在 RM 使用的 Ruby 1.8 中如何局限性地使用。
作者: 六祈    时间: 2010-9-10 00:33
本帖最后由 六祈 于 2010-10-22 21:58 编辑

2010-9-10
咱也来抢一楼,今天聊聊参数的话题吧~

比如def method_a(arg1 , arg2 , ……);end
这样子是最普通的~
method_a有两个参数分别是arg1和arg2,这两个会作为局部变量被传入到方法内部,完成方法并返回后应该会由GC回收

默认参数,是指参数有默认值
形如def method_b(arg1 , arg2 = 3);end
这样调用方法时,如果仅仅传入一个实参,则arg2为默认的3【默认参数需要放在常规参数后面】

剩余参数,指将个数不明的参数作为一个数组的元素传入
形如def method_c(arg1 , *args);end
则使传入的参数中,第一个赋值给arg1,其余的成为一个数组args,以args[0]等方式调用

另外,在方法调用的参数里也可以使用*,那个是将数组展开作为参数,如下:
def method_c2(arg1,arg2,arg3);end;method_c2(*[1 , 2 , 3]);end

关键字参数,指在形参最后用一个哈希表作为关键词参数
例子:def method_d(arg1 , keys = {});end
           而在调用时,可以这样写method_d(1 , {:a => 1 , :b =>2})【哈希作为最后一个参数时,可以省略大括号,称为裸哈希】
           则在方法体内部,就可以用用keys[:a]和keys[:b]分别取得1和2的值

【另外剩余参数和关键词参数不可同时使用,理由自己想吧】


另外再提下块,块也可以是参数,比如数组的迭代方法each,如果自己写的话,如下:
  1. class Array
  2.   def each(&block)
  3.     for i in 0...self.length
  4.       block.call(self[i])
  5.     end
  6.   end
  7. end
复制代码
这里的&指将方法调用时的代码块赋值给block这个变量,然后在方法体内迭代

而在方法调用时,也可以使用&,但意义不同,使用方法如下:
pro = Proc.new{|a| puts a}
[*1..8].each(&pro)
即可完成相当于:
[1,2,3,4……,8].each do |i|
puts i
end
的功能
即在实参中对一个变量前加&可以隐式调用该变量的【to_proc】方法并将之传入方法的代码块参数
作者: 八云紫    时间: 2010-9-11 00:33
标题: 2010-9-11
让我们来看看这个代码:
  1. def show_times
  2.    yield
  3.    yield
  4. end
  5. show_times { p "Hello World" }
复制代码
以上的运行结果是什么? 应该是出现两次 "Hello World".
为什么呢? 这个需要从 block 说起.

block 说的通俗点就是回调函数,所谓的回调函数就是一个通过函数指针调用的函数。
上面的代码其实可以这么理解, yield 就是调用方法内部提供的 block, 而这个 block 就是指 { p "Hello World" } 。这个和 数组什么的 each 一样。yield 其实可以简单的理解成先抢占一个位置,终于占这个位置为什么,等到需要的时候再决定。yield可以带参数。

这里只是一个无参数的例子,让我们再来看看这个例子:
  1. class Text_Class
  2.   def initialize(n, &b)
  3.     @n, @b = n, b
  4.   end
  5.   def show_message(index)
  6.     p "#{@n} is #{@b.call(index)}"
  7.   end
  8. end
  9. text = Text_Class.new("Text") {|index| index * 2}

  10. text.show_message(1) #=> 2
  11. text.show_message(4) #=> 8
复制代码
稍微复杂一点。一步一步来看。

先注意到 def initialize(n, &b) 里面的参数 &b 。如果参数变量之前带有 & 前缀的话,那么在定义的时候, Ruby会将 block 转换成一个 Proc ,再赋值给 b 这个参数。所以,上面的例子 text.show_message(1) 中, Ruby 会将数字 1 传给 index 变量,然后由声明的时候提供的一个块 {|index| index * 2} 来执行。

block 的用处很灵活,也很实用。最简单的一个实例就是 File 文件的操作,来看下面的例子:

  1. class File   
  2.    def File.open(*args) #将File.new方法需要的一篮子参数放到*args数组里   
  3.      result = f = File.new(*args) #将数组*args分散开并传给File.new , 这里的 *args 表示展开数组,而不是定义变长参数.
  4.      if block_given? #block_given? 方法属于Kernel模块,而此模块包含在Object中,   
  5.         begin #定义一个begin结构,以便即使有错误,也可以执行ensure语句   
  6.            result = yield f # f 传递到块中,result 接受了块的返回值。   
  7.         ensure   
  8.            f.close #不管有没有错误,都会在退出块时,可以自动的关闭文件   
  9.         end #可以确保缓存的东西都写入文件,并且可以交出占用的资源。   
  10.     end   
  11.     return result #如果不存在块,直接返回 result
  12.   end   
  13. end   

  14. File.open("Game.ini", "r") do |file|
  15.   while line = file.gets
  16.     p line
  17.   end
  18. end

复制代码
这个例子就更复杂了点,不过好在有注释。不过这里需要先知道这么几个小知识
1. block_given? 方法
   当某个方法有定义 block 的时候, block_given? 将返回 true。就像之前的例子一样。
2. begin...ensure...end
   异常处理,无论有没有异常发生,程序都会执行 ensure...end 之间的代码。
3. 定义后的 File.open 方法其实有两种行为,当有定义 block 的时候, 将执行 block ,并且退出 block 的时候将自动关闭文件,这样不是很方便么?
   其实上面的例子还可以写成:

  1. file = File.open("Game.ini", "r")
  2.   while line = file.gets
  3.     p line
  4.   end
  5. file.close
复制代码
相比之前的代码,是不是感觉冗余了好多。
作者: IamI    时间: 2010-9-12 08:17
(决不超过200字)
不用妄想修改RPG模块内类的initialize方法来追加数据,因为这个方法压根就没被调用过。
除非你的手上有method_defined?(不是关键字defined?),否则不要alias RGSS内建类。否则,F12君会生气的。
F12会抛出一个信号错误,叫做Reset。未定义?是在第一次抛出时被定义。
除非你用了紫苏的句柄脚本,否则一切句柄皆不可靠。同时开俩游戏,第一个就玩完。
手机打字好痛苦…OTL
作者: 紫苏    时间: 2010-9-13 02:57
(为了控制在 200 字以内决定采用打酱油模式)

喜欢 C printf 语法的人在 Ruby 中也得到了满足——Kernel::sprintf 是 C sprintf 的面向对象版,它不需要调用者传递缓冲区,而是自己(被调用者)分配一个字符串对象返回给调用者。它的基本作用是格式化字符串,输出按照指定格式格式化的一串数据,而这些数据本身可能不是字符串:
  1. p sprintf('大家好,我是 %s,我今年 %d 岁。', '张三', 23)
复制代码
这里即是把 '张三' 这个字符串对象(对应 %s 指示符)和 23 这个 Fixnum 对象(对应 %d 指示符)格式化到了前面的那句话中。
实际上 Ruby 还提供了更简便的方法,就是 String#% 方法。以下代码等价于以上代码:

  1. p '大家好,我是 %s,我今年 %d 岁。' % [ '张三', 23 ]
复制代码
% 前面是格式,后面是格式中指定了的额外数据,如果额外数据只有一个,那可以省略数组的外壳:
  1. p '大家好,我今年 %d 岁。' % 23
复制代码
对 printf 语法有兴趣的朋友可以参考:http://www.cplusplus.com/reference/clibrary/cstdio/printf/
作者: 紫苏    时间: 2010-9-17 08:00
本帖最后由 紫苏 于 2010-9-17 08:03 编辑

左值——赋值运算符 `=' 左边的值
右值——赋值运算符 `=' 右边的值
Ruby 有一个继承自 Perl 的语法糖:平行赋值,它缩短了赋值语句的代码体积。平行有“并发”之意,即同时赋值,互不干扰。
  1. a,b,c=1,2,3
复制代码
这是最基本的平行赋值语法,1、2、3 按出现顺序被分别赋给了 a、b、c。
当最后一个右值是带了星号前缀的数组对象时,数组会被展开并把元素依次赋给对应的左值:
  1. arr = [ 2, 3 ]
  2. a,b,c = 1,*arr
复制代码
这和第一句代码的运行效果没区别。
当右值只有一个,而左值不止一个时,右值就会被当做或转换为数组对象,并依次把元素赋给匹配的左值:
  1. a,b,c = Time.now.to_a
  2. p a,b,c
复制代码
虽然平行赋值在实现层必然不是真正意义上的平行,但经过 Ruby 的抽象层后,在用户看来这确实是平行的,以下代码可以用来交换两个变量的值,而如果赋值有先后顺序(不并发),交换则不会成功:
  1. a,b = b,a
复制代码
如果左值数量多于右值,没有匹配右值的左值会被赋为 nil;如果右值数量多于左值,没有匹配左值的右值会被忽略。
如果最后一个左值有星号作为前缀,那么 Ruby 会保证它接收到的是一个数组。如果这时左值数量少于右值,那么剩下的右值会被一起封装到数组中赋给这个左值:
  1. *c = 1,2,3,4
  2. p c
复制代码
还可以给左值添加括号进行“嵌套赋值”,解决了数组形式的右值不能带着星号前缀出现在右值列表尾部之前的问题:
  1. (a,b,c),d = [1,2,3],4
  2. p a,b,c,d
复制代码
Ruby 的块参数的传递、Proc 参数的传递都使用平行赋值的规则:
  1. def foo
  2.   yield [1, [2, 3], 4], *[5, 6]
  3. end

  4. foo { |(a,(b,c),d),e,f| p a,b,c,d,e } # a、b、c、d、e 分别被赋予了 1、2、3、4、5
复制代码

作者: 紫苏    时间: 2010-9-23 07:35
其实话题不少,但两百字能说清楚的比较少,还是每天来打一瓶酱油好了 <--- 这是闲话

今天来说一下跳出两层以上循环的方法,为了节省字数每天只说一种方法——
1、使用内置的异常机制,在多层循环内部抛出一个异常,在多层循环外捕获这个异常即可:

  1. begin
  2.   for i in 1..10
  3.     for j in i..10
  4.         p i, j
  5.         raise if i + j > 12 # 跳出两层循环
  6.     end
  7.   end
  8. rescue
  9.   p '跳出了循环'
  10. end
复制代码

作者: 紫苏    时间: 2010-9-24 06:50
本帖最后由 紫苏 于 2010-9-24 07:01 编辑

承接二十一楼:
2、在当前局部范围内使用闭包块进行跳转:
  1. lambda {
  2.   for i in 1..10
  3.     for j in i..10
  4.         p i, j
  5.         return if i + j > 12 # 跳出两层循环
  6.     end
  7.   end
  8. }.call
  9. p '跳出了循环'
复制代码
当然,如果你的循环过程独立于其它流程,那么用方法也是可以的;否则由于 Ruby 1.8 的方法没有进行上下文绑定(而闭包则保持了闭包被创建时那一刻的绑定),在方法内部是无法引用外部的变量的,因为它们分属于不同的栈帧,互不可见(正因如此,方法内的变量才被称为局部变量)
作者: 禾西    时间: 2010-9-25 08:07
今天沒有人搶耶?那麼就由我來主持吧XD
Ruby 的事情說的人多了,就來談談RGSS的東西~

在RM當中,圖片的緩存是由 Cache 模板處理的。它的實現機制是用名為@cache的hash表記錄bitmap和原路徑的對應關系,從而使得不需要重複生成 bitmap (因為這個東西的生成代價是很高的)。
這是最核心內容:

  1.   def self.load_bitmap(path)
  2.     if not @cache.include?(path) or @cache[path].disposed?
  3.       @cache[path] = Bitmap.new(path)
  4.     end
  5.     return @cache[path]
  6.   end
复制代码
這樣做的優點是可以減少讀取文件次數,從而提高程序速度;缺點是占用內存太多,低配置機械跑不動。而值得注意的是,當你對一個bitmap進行繪畫處理時,操作會直接影響到bitmap的實例,因此大家不要在存入 cache 的 bitmap 上面寫字和作畫哦。

除開頭和結尾的字數不算,剛好200XD
作者: goahead    时间: 2010-9-25 11:08
提示: 作者被禁止或删除 内容自动屏蔽
作者: 六祈    时间: 2010-9-26 17:52
本帖最后由 六祈 于 2010-9-26 17:57 编辑

每日一酱油【这行不算字】

有关map的一个小技巧

【技术基础:实参前添加&可以隐式调用to_proc方法】
  1. class Symbol
  2. def to_proc
  3. proc {|obj , *args| obj.send(self , *args)}
  4. end
  5. end
复制代码
上面的代码定义了Symbol对象的to_proc方法
然后~赋值一个字符串数组
  1. words = %w(hello kitty not world)
复制代码
然后是重点:
  1. list = words.map(&:capitalize)
复制代码
ok,得到了第一个字母大写的字符串数组了
作者: 六祈    时间: 2010-9-27 02:20
本帖最后由 六祈 于 2010-9-27 18:28 编辑

继续每日一酱油~~~【这行依然不算字】

今日话题:你真的会用[]吗?
String#[]

国际惯例,先赋值一个模范变量
  1. str = "hello , world"
复制代码
[start,range]
  1. p str[0,5]
复制代码
这个会打印从索引0开始,取到的5个字符

[start..end]
  1. p str[0..4]
复制代码
这个会打印索引0到索引4【包含】的字符串

[String]
  1. p str["hell"]
复制代码
如果字符串能匹配hell的话会打印之

[integer]
  1. p str[0]
复制代码
这个会打印一个数字哟~要谨慎使用,如果想要打印索引0的字符,要使用
  1. p str[0].chr
复制代码
哟~

[Regexp]
  1. p str[/h.+?o/]
复制代码
会打印匹配正则的字符串部分,结果显示hello
作者: goahead    时间: 2010-9-28 10:31
提示: 作者被禁止或删除 内容自动屏蔽
作者: goahead    时间: 2010-9-29 09:07
提示: 作者被禁止或删除 内容自动屏蔽
作者: goahead    时间: 2010-9-30 10:36
提示: 作者被禁止或删除 内容自动屏蔽
作者: 紫苏    时间: 2010-10-3 01:23
由于在不远的(希望...)将来, RGE 2.0 会嵌入 Ruby 1.9 作为脚本语言,它解决了很多 1.8 潜在的性能问题(当然,由于多出来的很多新东西,比如枚举器,也在某些方面降低了性能),我认为在这里讨论一下 1.9 的新东西也是有意义的。

Ruby 1.9 的类继承图表根部不再是 Object,而是一个叫 BasicObject 的类。为什么需要这个类?须知 Object 虽然处在底层,但也是捆绑了大量方法的庞然大物,它包含了在 Kernel 中定义的所有基本对象的实例方法。我们有时可能希望使用一个货真价实的“老子”,没有从任何人那里继承任何东西,以节省对象虚表所占据的空间,这就是 BasicObject 被引入的主要目的。它只包含了仅有的几个基本方法,没有包含克隆、污染、安全、冻结、反射、动态求值等五花八门的对象功能。
  1. p BasicObject.instance_methods(false)
复制代码
这一行是打印出所有 BasicObject 定义的实例方法,输出:
[:==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__]
而如果我们:
  1. p Object.instance_methods(true)
复制代码
则输出:
  1. [:nil?, :===, :=~, :!~, :eql?, :class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :to_s, :inspect, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :define_singleton_method, :hash, :__id__, :object_id, :to_enum, :enum_for, :gem, :==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__]
复制代码
可见差距。后面之所以传递了一个 true,是为了同时输出从父类继承过来的方法,因为 Object 多出来的这些方法来自 Kernel Mix-in(换言之,这些方法本身定义在 Kernel 中),所以实际上 Object 有一个匿名父类,存在于 Object 和 BasicObject 之间。

在 Ruby 1.8 中,我们可以通过 undef 去实现这样的一个 BlankSlate (白板)类:
  1. class BlankSlate
  2.   instance_methods.each { |m| undef_method m }
  3. end

  4. p BlankSlate.instance_methods
复制代码
当然,有些方法你还是想保留的,比如 object_id、__send__(实际上如果你 undef 了这两个最基本的方法,Ruby 解释器会给你警告:
warning: undefining `object_id' may cause serious problem
warning: undefining `__send__' may cause serious problem

但是这仍然是一种亡羊补牢的做法。我们希望从一开始就有这样的“白板”,于是就有了 BasicObject。
作者: 紫苏    时间: 2010-10-4 04:27
Ruby 1.9 支持一级函数,匿名闭包也成了语言的基本元素之一。用户可以通过 `->' 这个符号来快速写出一个 lambda 函数:

  1. a = ->(a, b) { a * b }
  2. p a.(3, 4)    # 12
复制代码
而这等同于 Ruby 1.8 的:

  1. a = lambda { |a, b| a * b }
  2. p a.call(3, 4)    # 12
复制代码
相比之下,新的 lambda 语法看起更像函数,也更接近 Lisp 风格的闭包。
前面的 a.(3, 4) 也是一个新的语法糖,它使得我们可以省略掉 `Proc#call'(当然,它底层调用的还是 call,所以它只是一个语法糖)。
Matz 为什么要用 `->' 这个符号来表示 lambda 函数?Ruby 社区一致认为内置运算符应该只使用 7 位 ASCII 以内的字符,所以他不能直接用 ` λ' 这个并不在 7 位 ASCII 中的字符。按照他的说法,`->' 这个符号会让人想起 lambda 的原型 ` λ',所以就选用了它。不要和 C/C++ 的成员、指针运算符弄混淆!
作者: DeathKing    时间: 2010-10-5 07:12
本帖最后由 DeathKing 于 2010-10-5 07:14 编辑

本来6点就起来了,但是打了一会儿酱油。

  1. ARGV << "a.c"
  2. print while gets
复制代码
gets 是 Kernel 中的方法,用于取得 ARGV 中的各行内容。将文件名 a.c 放入 ARGV 中,让 gets 不断取回(直到到文件尾)。

还能快点么?
  1. print IO.read("a.c")
复制代码

作者: 紫苏    时间: 2010-10-6 07:04
Ruby 1.9 的块的参数也可以使用 & 来把默认的块转换为 Proc 对象了:
  1. l = lambda { |&l| l.call }
  2. l.call { p 'nested' }
复制代码
而这在 1.8 中则是不合其语法的。相同的效果也可以由我们之前提到的 `->' 运算符实现:
  1. l = ->(&l) { l.() }
  2. l.() { p 'nested' }
复制代码

作者: 紫苏    时间: 2010-10-7 07:55
Ruby 1.9 支持带缺省值的参数出现在不带缺省值的参数之前,而 Ruby 1.8 及其它很多支持参数缺省值的语言都要求带缺省值的参数只能出现在列表最后。

  1. def foo(a = 0, b)
  2.         p a+b
  3. end

  4. p foo(2)       # => 2
  5. p foo(1, 2)  # => 3
复制代码
当然,带缺省值的参数必须挨在一起,如下情况是不合语法的:
  1. def bar(a, b = nil, c, d = nil, e)
  2. p [ a, b, c, d, e ]
  3. end
复制代码
这可能是由于 Ruby 解释器使用的 LR(1) 解析器不能轻易处理这种情况。
带缺省值参数和可变长度参数列表可以混用,但前者必须出现在后者之前:
  1. def foo(a = 0, *args)
  2.     p [a, args]
  3. end

  4. foo(5, 6, 7, 8) # => [5, [6, 7, 8]]
  5. foo()           # => [0, []]
复制代码

作者: 紫苏    时间: 2010-10-8 07:01
本帖最后由 紫苏 于 2010-10-8 07:13 编辑

Ruby 1.9 引入了新的语法支持块局部变量,同时也改变了原来的块参数行为。在块原本的参数列表中,使用一个分号 `;' 可以分隔块的参数和局部变量声明,如下:
  1. l1 = l2 = 1
  2. lambda{ |arg1, arg2 ; l1, l2|
  3.         l1 = 1234567
  4.         l2 = 7654321
  5.         p "参数是#{arg1},#{arg2}"
  6. }.call(126, 127)
  7. p l1                                        # => 1
  8. p l2                                        # => 1
复制代码
其中 arg1、arg2 是块的参数(call 的时候需要传递),而 l1、l2 则是块局部变量的声明。块局部变量的作用域仅限于块的上下文,且声明的块局部变量必须出现在块参数后面。
这种机制也是为了进一步完善闭包,毕竟早在 Lisp 时代就有 let 语法支持闭包局部变量。这实际上就是变量遮蔽机制在闭包这个场合的一个特例——作用域小的变量遮蔽了作用域大的变量,使得作用域大的变量不可见。
块的参数在 Ruby 1.8 中并不一定是局部的,如果外部有同名变量,那么它引用的仍然是外部的同名变量。在 Ruby 1.8 中,以下代码中的块变参数赋值会改变块外部变量的值(也就是没有了遮蔽):
  1. l1 = l2 = 1
  2. lambda{ |l1, l2|
  3.         p "参数是#{l1},#{l2}"
  4.         l1 = 1234567
  5.         l2 = 7654321
  6. }.call(126, 127)
  7. p l1                                        # => 1234567
  8. p l2                                        # => 7654321
复制代码
而在 Ruby 1.9 中,块的参数永远是局部的。
作者: 紫苏    时间: 2010-10-9 08:35
Ruby 1.9 有了新的 Hash 结构和语法。在 1.9 中,你可以用以下方式更快速地写出一个包含以符号为键的元素的 Hash 对象:
  1. #coding: GBK
  2. p ({ 赵天吉: "大刀会", 朱红灯: "义和团" })
复制代码
输出:
{:赵天吉=>"大刀会", :朱红灯=>"义和团"}
注意这种新的 Hash 字面值只针对于符号键。
结构方面,也有了一个大革命。Hash,顾名思义,一堆杂乱的符号,自然没什么顺序可言,随着我们对 Hash 的了解越深,“Hash 不保证顺序”的概念就越来越深入我们脑海。然而这次,顽皮的 Ruby 又一次颠覆了用户的观念:Hash 也可以是有序的!
在 1.8 中,Hash 对象即是使用分离链接法解决哈希碰撞的散列表。表中每一项需要四个东西:散列码、键的引用、值的引用、下一个散列码相同的元素的引用。为了实现有序的散列表,Ruby 1.9 在表的元素结构体中增加了两个新的指针:指向按顺序排列的前一个和后一个元素的指针,这使得在对散列表进行插入和删除操作时能够维护一个有序的链,实际上就是一个和旧的散列表结构穿插在一起的双向循环链表结构,这两种结构的并集就是新的 Hash 结构了。这个改动的代价是:1.9 的 Hash 的 插入和删除变得更慢了些。这个改动的好处是:Hash 的遍历、迭代、枚举也有序了。同时,因为内部维护着双向循环链表,1.9 遍历 Hash 的速度也比 1.8 快,因为 1.8 必须要遍历整个散列表的桶,自然也就必须经过一些桶中的空位。
对于为了有序遍历而损失插入、删除的效率是否值得的问题,仁者见仁,智者见智。有些人用散列表来存储一种字典式的结构,然后需要把内容按照有序的方式打印出来,这是常有的;而有些人仅仅是用散列表来进行快速的保存和读取数据。不过我个人倒是有点不赞成直接改动原来的 Hash 类,我们完全可以设计一个新的类,比如 OrderedHash,这样就能让用户有一个选择。
作者: 紫苏    时间: 2010-10-10 10:09
本帖最后由 紫苏 于 2010-10-13 19:54 编辑

Ruby 1.9 引入了纤程(Fiber)的概念。
提起纤程,我们自然而然地就想到了线程(Thread),而事实上当你对比了“纤”和“线”这两个字后,你大概就能猜到,纤程实际上是一种轻量级的线程了。轻量级在这里是指纤程的体积比线程小,更容易维护、调试,但也不具备一些线程才能提供的复杂功能。
在并行计算领域,我们使用线程来构建一个抢先式多任务模型;而纤程则是用于合作式多任务模型。
抢先式多任务模型,顾名思义,是指执行任务的对象的工作模式是抢先式的,工作调度完全由一个具有权利的、客观的存在进行。比如:三个兄弟抢着用电脑,谁也不让谁,但是当妈妈在家的时候,就能够合理地分配、调度、切换各个兄弟使用电脑的时间。又如:我们使用的操作系统,其进程和线程的调度就是一种抢先式任务模型。
合作式多任务模型,显然就是指执行任务的对象之间有团队精神,可以协同工作。第一个对象完成了一部分工作,就可以把成果交给第二个对象,让他在成果的基础上继续工作,并等待他完成。所以,我们使用纤程的主要目的,就是实现这种所谓的“协程”。比如:动物园的售票系统模型,观光者需要售票员处理售票相关的事项,然后把票递给自己;而售票员需要观光者把钱递给自己。这是在纤程的概念还没有出现时就存在于线程技术中的生产者与消费者关系的概念。
来看一个简单的例子:

  1. fibonacci = Fiber.new {
  2.         a = b = 1
  3.         loop {
  4.                 b += a
  5.                 a = b - a
  6.                 Fiber.yield b
  7.         }
  8. }

  9. 10.times {
  10.         p fibonacci.resume + 1000
  11. }
复制代码
输出:
1002
1003
1005
1008
1013
1021
1034
1055
1089
1144

我们这里是实现了打印出一个 Fibonacci 序列中前 10 个数 各加上 1000 后的结果的协程。fibonacci 是我们的生产者纤程,它的任务是生产出 Fibonacci 序列。在循环内部,Fiber.yield 会暂停执行,把执行权让给调用纤程的代码,并把参数作为 Fiber#resume 的返回值传递回去。在纤程外部,我们有一个十步的循环,作用是把 Fibonacci 序列前 10 个数各加上 1000 并打印出来。每次调用 Fiber#resume,都会恢复纤程的执行,并返回 Fiber.yield 的参数。这时如果我们给 resume 传递了参数,该参数就会被作为 Fiber.yield 的返回值传递给纤程(当然我们这里没有用到这个功能,但在模拟更复杂的合作式多任务时,是极有可能需要双向交互的)。
同样的效果,我们可以用一个循环就完成,即在生产 Fibonacci 数的同时打印(循环内部生成一个就打印一个),但这被认为是差劲的设计模式,因为它把生产和消费这两个过程混合到了一起。使用纤程的话,我们就能使它们泾渭分明。
同样的效果,我们也可以用线程来实现。但线程相比于纤程是重量级的,我们需要考虑对象锁之类的条件变量来进行线程同步,同时还得考虑潜在的线程安全问题。而我们的纤程,则拥有同步的天性,使得我们在使用时无须顾虑各种线程相关的复杂问题。
Ruby 1.9 内置的纤程只适用于小规模协程(二人合作),因为 Fiber.yield 只能把执行权返回给 Fiber#resume 的调用者。如果想实现规模更大的协程,我们就需要使用 Ruby 1.9 标准库中的 Fiber 了(不属于这帖讨论范畴)。
作者: 逸豫    时间: 2010-10-10 11:12
为什么此帖被顶上来了- -
然后不留些东西貌似过意不去……
RM的Interpreter是个很诡异的存在,尤其是数据结构保存形式- -
显示文章等带换行的数组保存形式是
事件开始代码
事件开始代码+400(貌似脚本是300)
同时……在eval脚本的时候有一处诡异的地方:
  1.     # 评价
  2.     result = eval(script)
  3.     # 返回值为 false 的情况下
  4.     if result == false
  5.       # 结束
  6.       return false
  7.     end
复制代码
这样导致了如果事件页脚本返回的是false的话就会导致index不能推进,事件卡住的问题- -
于是当脚本是赋值变量为false时只能占用那少得可怜的脚本框写下return true之类的结束语……而且这种错误通常无法发现- -
最后……感谢II提醒脚本卡住的原因- -
作者: DeathKing    时间: 2010-10-11 12:45


Kernel、Object、Module、Class间的关系。

简洁、明了、没有胃溃疡。
作者: zh99998    时间: 2010-10-13 16:58
看紫苏大人讲了好多1.9特性,于是我也来弄一下。。。不过级别完全不够所以只说个自己用过的小细节

在1.8中,取string里的一个字符会得到一个整数,其值是那个字符的ASCII码,除非你是C语言控,否则你不会认为这是一项功能而是一个bug
指的庆幸的是在1.9这个缺陷得到了改变,取出的将是一个只包含那个字符的string对象

a = "ABC"
p a[0]
#=> 65 in 1.8
#=> "A" in 1.9

ruby1.9乃太萌了,紫苏大人给点资料吧- -最好有中文
作者: 紫苏    时间: 2010-10-15 08:16
本帖最后由 紫苏 于 2010-10-16 00:59 编辑

Ruby 1.9 的一大改进就是对编码的支持有了质的飞跃。今天我们先来看一下 1.8 中令人头疼的字符串编码、解码相关的问题是如何在 1.9 中轻易解决的。
内置库中,String 类多了几个方法——encode、encode!、encoding,前两个都是把接收者字符串按照另一个指定字符集编码的方法,分别是非破坏性和破坏性版本;第三个是用来获取编码方式的访问器。

  1. #encoding: UTF-8
  2. p 'hello'.encode('UTF-16LE')
复制代码
这里顺便介绍一下第一行的作用,它是 Ruby 1.9 的新功能。什么,这不就是行注释?是的,你没看错,它确实是一行注释,但却被称为“magic comment”,因为它是 Ruby 1.9 推荐的用来指定源文件编码的方式。如果一个 Ruby 源文件第一行包含注释,且注释中包含 `coding: ' 这个字串(coding + 冒号 + 空格),那么在 `coding: ' 之后的内容就会被用来作为当前源文件的编码名称,如这里的 UTF-8。在类 Unix 操作系统上,源文件可以包含 Shebang,而 magic comment 则必须是紧接着 Shebang 的下一行,不能有空行。
所以,我们这里是让源文件按照 UTF-8 格式来编码,而这会使得字符串字面值的编码也成为 UTF-8。之后我们通过 String#encode 方法把一个简单的字符串转换为了小端序 UTF-16 编码,也就是平时我们引用 `Unicode' 时所指的编码,这是我们在 Windows 操作系统上调用 API 处理多字节字符(如:中文字符)相关的操作时需要的编码方式。输出:

"h\x00e\x00l\x00l\x00o\x00"

我们就是这么简单地把字符串编码为了本地 API 可识别的形式,而在 Ruby 1.8 中,还得通过调用本地 API 来进行转码,如 Windows 平台上的 MultiByteToWideChar 函数。
如果我们没有给 String#encode 传递参数,那么它会默认使用 Encoding.default_internal(这个以后再讲)来编码。如果传递了两个字符串参数,那么后一个字符串参数则会被作为源编码方式:
  1. p '你好'.encode('UTF-16BE', 'GBK')
复制代码
这里就是把 '你好' 这个字符串当做 GBK 字符串编码为大端序 UTF-16。
String#encode 还接受第三个参数,这个参数指定了编码时额外的选项,包括源字符串中的非法字符以及目标编码中不存在对应代码点的字符的默认替代字符的选项(比如 `?',我们经常能在某些非 Unicode 程序中看见代码页不匹配时出现的一大串 `????',就是因为这些程序用来解码的字符集中没有对应的源字符串中的代码点,这时程序就选择用 `?' 来代替这个它不知道如何表示的字符),不同平台之间回车、换行符转换的选项(Windows 是 `\r\n', Linux 是 '\n', Mac OS 是 '\r')以及 XML PCDATA 特殊字符转换的选项(如 < 转换为 &lt; 等),具体可以参考官方文档中的详细表格,这里给一个处理中文时的情况:

  1. #encoding: GBK
  2. p '你好'.encode('ISO-8859-1')
复制代码
以上会抛出一个 Encoding::UndefinedConversionError 异常,因为 ISO-8859-1 没有中文字符的代码点,而我们又没有指定替换字符。这样的话:

  1. #encoding: GBK
  2. p '你好'.encode('ISO-8859-1', undef: :replace, replace: '?')
复制代码
就可以了,输出:"??"。要注意这里后面实际上是一个参数,是一个以符号为键的 Hash 对象(见本贴之前的相关内容)。

String#encode! 方法和 String#encode 用法一模一样,只不过是原地(破坏性)算法。String#encoding 可以用来获取接收者字符串当前的编码方式。

String#encode 和 String#encode! 表式编码的参数不一定是编码名称字符串对象,也可以是一个 Encoding 对象。关于 Encoding 对象,我们下次再讲解。
作者: 紫苏    时间: 2010-10-16 06:33
本帖最后由 紫苏 于 2010-10-16 06:38 编辑

通过上一讲,相信同学们都对 Ruby 1.9 的字符串编码有了一定的了解。在底层,1.9 的编码功能实际上是通过一个叫 M17N 的开源引擎实现的,专门用来处理多语言字符串,它在 C 的层面把字符串包装成了一个拥有一些属性的对象。
用心的同学们肯定会记得,我们曾经提到了 Encoding 对象这个概念。Encoding 对象,自然是表示一个编码对象,我们可以用它来代替用来表示编码名称的字符串。Encoding 这个类没有提供实例化的方法,但是它在自己的命名空间中预定义了大量的 Encoding 对象作为静态成员,各自表示一种 Ruby 1.9 支持的编码方式。这是一种经典的设计模式——多例(multiton)模式,与之类似的有单例(singleton)模式,即类只有一个单一实例的模式。

  1. p Encoding.name_list
复制代码
这句脚本会打印出当前支持的所有编码。类似于 name_list 这样的静态方法(类对象单例方法),还有:
Encoding.aliases,它返回一个以别名为键,基础编码名称为值的 Hash 对象,告诉了你所有编码的所有别名,如 CP936 就是 GBK 的别名;
Encoding.compatible?,它测试两个字符串的编码是否兼容,兼容编码的字符串可以直接串连起来;
Encoding.default_external、Encoding.default_external=,它们分别获取、设置默认的外部编码,用于处理输入、输出流时的字符串编码;
Encoding.default_internal、Encoding、default_internal=,它们分别获取、设置默认的内部编码,用于内部数据读写的转码;
Encoding.find,通过编码名称字符串获取对应的 Encoding 对象;
Encoding.list,罗列出所有加载了的 Encoding 对象(之前提到的 name_list 是编码名称的字符串);
Encoding.locale_charmap,返回当前 locale 的代码页,这是由操作系统的设置而决定的,比如 PRC locale 的代码页就是 GBK;

Encoding 的实例也可以通过预定义的常量来引用,如:Encoding::UTF_8、Encoding::GBK。
Encoding 实例的方法,有 dummy?、name 和 names 这三个。dummy? 是用来测试接收者 Encoding 对象是否是 dummy 的(很明显!),即 M17N 并没有真正地实现、支持这种编码,但 Ruby 仍然把它加入了进来,如:UTF-7;name 显然是返回编码的名称;names 则是返回接收者编码对象的所有有效名称:

  1. p Encoding::UTF_8.names
复制代码
输出:
["UTF-8", "CP65001"]
作者: 紫苏    时间: 2010-10-17 05:24
本帖最后由 紫苏 于 2010-10-17 05:27 编辑

继续一些和 Ruby 1.9 字符串编码有关的内容——

[line]1[/line]

新的转义序列:\u
在字符串字面值中指定 \u 可以用来表式一个 Unicode 字符,后面紧跟着一个 16 位(双字节 UTF-16BE)十六进制数字:

  1. #coding: GBK

  2. str = "测试".encode(Encoding::UTF_16BE)
  3. p "\u6d4b\u8bd5".encoding
  4. p "\u6d4b\u8bd5".encode(Encoding::GBK, Encoding::UTF_8)
  5. p "\u6d4b\u8bd5".encode(Encoding::GBK)
复制代码
(注:这里的源文件编码使用 GBK 是因为我测试时的源文件是 GBK 编码,下同。)
输出:

#<Encoding:UTF-8>
"测试"
"测试"

当字符串中包含 \u 序列时,尽管你指定的是 UTF-16 大端序的 Unicode 字符,该字符串还是会被自动编码为 UTF-8,并相应设置字符串的编码成员为 UTF-8(默认是和源文件编码相同),所以这里第一行打印出了一个 UTF-8 的 Encoding 对象。

`\u6d4b' 和 `\u8bd5' 这两个字符分别是 `测' 和 `试' 的 UTF-16BE 内码。后两行输出语句是完全一致的,因为字符串对象内部维护着一个编码对象的域,所以 String#encode 可以根据这个域来进行编码。这里它知道 "\u6d4b\u8bd5" 的编码为 UTF-8,所以是否手动指定源编码都无所谓了。由于我测试时使用的是 Windows 的字符终端(即被称为“命令提示符”的控制台),它使用的编码取决于 locale,而我的 locale 的代码页是 GBK,所以只有当我们把这个字符串编码回 GBK 后,才能正常地在字符终端上显示“测试”这两个字。

注意我们这里用的是 Encoding::UTF_8 等常量,而不是 "UTF-8" 这样的字符串,这重利用了预先生成的 Encoding 对象,避免了字符串对象的创建,给 Ruby 的堆和 GC 减少了压力。

[line]1[/line]

String 进行了 Comparable 的 mixin,糅合了一个 `==' 方法,但 String 类对它进行了覆盖,使其比较字符串内容而不是对象的身份。现在 String 多了编码的成员,但 == 并不会进行不同编码间相同字符串的验证,它只是简单地比较每一个字节。

[line]1[/line]

String#codepoints 也是一个新的方法。它返回字符串中所有代码点的枚举器(关于枚举器,我们改天再讨论,如果你还没有概念,可以想象成一个集合,与数组相似但还并未完全是数组)。代码点是一个字符在代码页中对应的、唯一的整数,这些整数的集合就形成了一个“代码空间”(即字符集),比如 7 位 ASCII 字符集,就有 128 个代码点,分别表示 128 个不同的字符。在一个字符集中,一个代码点和一个字符是有一对一关系的,和字节是两码事。比如 `测' 这个字符在 UTF-8 里占据 3 个字节,但是与之对应的只有一个代码点。

  1. #coding: UTF-8

  2. p "测试".codepoints.to_a
复制代码
输出:

[27979, 35797]

你可能发现这两个数字刚好是我们之前看到的 `\u6d4b'、`\u8bd5' 的十进制表示,但这并不代表 UTF-8 和 UTF-16 没区别。UTF-8 和 UTF-16 等 Unicode 编码家族都可以使用相同的代码点和相同的 Unicode 平面,但是在数据存储的细节上有不同。

[line]1[/line]

String#force_encoding,它可以强行改变字符串对象的编码属性(一个 Encoding 成员变量),但它并不会改变底层的内码,而只是改变其它编码的方法(如 String#encode)处理字符串时的行为。
  1. #coding: GBK

  2. str_utf_8 = "测试"
  3. str_gbk = str_utf_8.clone
  4. str_gbk.force_encoding(Encoding::UTF_8)

  5. p str_gbk.encode(Encoding::GBK)
复制代码
输出:

1.rb:7:in `encode': "\xB2" on UTF-8 (Encoding::InvalidByteSequenceError)
        from 1.rb:7:in `<main>'

String#encode 把 str_gbk 当做 GBK 编码来处理,而 str_gbk 的内码实际上还是 UTF-8,所以抛出了这个异常。用户可以用 String#valid_encoding? 来对此进行判断。
作者: 紫苏    时间: 2010-10-18 10:50
本帖最后由 紫苏 于 2010-10-19 00:48 编辑

连续弄了三天编码,同学们想必都有点腻了,老实说我自己也有点反胃,还剩下 I/O 部分的一些编码相关内容,就留待改日补完了吧!
(以下提及的 Ruby,均指官方的 Ruby 实现)
今天我们来简单的看一下提升 Ruby 1.9 性能的大功臣——YARV。
YARV,全称是 Yet Another Ruby VM,VM 的全称是 Virtual Machine。这个名称,翻译为中文,就是“又一个 Ruby 虚拟机”。这种以 `Yet Another' 形式开头的命名是一种黑客行话,在有英语语感的程序员看来,这种命名有三分幽默诙谐之感,在业内十分受欢迎。
Ruby 的虚拟机,就和 Oracle 的 JVM(Java 虚拟机)、微软的 CLR(.NET) 一样,是一种由软件实现的虚拟的机器构架,有着自己的指令集,并可以像物理机器一样执行指令。这种针对某个(家族)语言设计的虚拟机被称为进程虚拟机,因为它只是给操作系统上某一个进程(解释器)服务的;与之并列的是系统虚拟机,它需要分享整个系统的物理硬件资源,比如我们 VirtualBox 下的 Guest OS,就是一种虚拟一整个操作系统的技术。
为什么 Ruby 需要虚拟机?大多数程序员都是人类,人类写的代码,通常是按照人类思考的方式去对机器下达命令,但并不一定有着最高的执行效率。同样效果的代码,在很高程度上都存在着另一份更高效的途径,而我们把找寻并迈向这个途径的过程称为优化。对于编译性语言,我们可以设计一个具有自动优化(同时从源代码层、汇编层)的编译器来一次性最优化我们的代码,并编译为机器码;而对于解释性语言,由于我们是在动态地解释脚本,所以不能把代码一次性地优化并编译为机器码。
Ruby 1.8 的解释器被称为 Matz's Ruby Interpreter(MRI),它解析 Ruby 源文件,并生成一个语法树,然后直接在这个语法树上进行动态求值。这样的过程是毫无优化的。目前主流的解决方案大致可分为两种,其一是采用预编译(Ahead-of-time Compilation),其二是采用即时编译(Just-in-time Compilation)。就 Ruby 1.9.2 的 YARV 来说,它只有前者。所谓预编译,顾名思义,就是预先把代码编译。什么,我之前不是说过“不能把代码一次性地优化并编译为机器码”?是的,的确是这样,但这里的“预编译”不是编译为机器码,而是编译为一种中间代码,通常被称为字节码。字节码和机器码的不同在于,前者是平台无关的,且具备动态性,是由解释器(虚拟机)动态解析的,而不像机器码是直接交给底层的硬件(CPU)去执行。在把可供人类阅读的源代码编译为字节码的过程中,我们就可以进行各式各样的优化,最后产生的字节码,在大部分场合下都比原来的源代码更加高效。
(题外话: YARV 的作者 Sasada Koichi 也曾尝试过引入 JIT 即时编译,进一步提高 YARV 的性能,但 YARV 的主页很久没有更新了,也不知进展如何。JIT 本身是一个十分复杂、很难实现的技术,可能对他来说并不是一个人能实现的了的。也许,Ruby 2.0 会……)
YARV 就是这样的一个虚拟机,它忠心耿耿地优化着我们所写下的 Ruby 代码,几十年如一日,在大量的 Benchmark 测试下,展示了 Ruby 1.9 几近于 Ruby 1.8 两倍的性能。

在 Ruby 1.9 的官方实现(CRuby)中,有了一个内置的类,叫做 RubyVM。它提供给了用户一些在运行时获取 YARV 状态的实用接口,这里介绍几个比较重要的类和方法。
RubyVM::InstructionSequence,这是一个 YARV 指令序列的类,它可以用来动态地编译、反汇编、执行 Ruby 代码。

  1. instrs = RubyVM::InstructionSequence.compile <<-START
  2.         a = 0
  3.         a = a + 1
  4.         a += 2
  5.         p a
  6. START

  7. puts instrs.disassemble
复制代码
这里我们先编译了四行简单的 Ruby 代码,并建立了一个 RubyVM::InstructionSequence 对象。之后我们把反汇编后的指令序列按照 Ruby 提供的可供人类阅读的方式打印了出来,输出:

== disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 2] a
0000 trace            1                                               (   1)
0002 putobject        0
0004 setlocal         a
0006 trace            1                                               (   2)
0008 getlocal         a
0010 putobject        1
0012 opt_plus
0013 setlocal         a
0015 trace            1                                               (   3)
0017 getlocal         a
0019 putobject        2
0021 opt_plus
0022 setlocal         a
0024 trace            1                                               (   4)
0026 putnil
0027 getlocal         a
0029 send             :p, 1, nil, 8, <ic>
0035 leave

可以看到,四行 Ruby 代码在 YARV 层实际上就是这样的指令序列。我们可以通过 RubyVM::INSTRUCTION_NAMES 来打印出 YARV 所有的指令:

["nop", "getlocal", "setlocal", "getspecial", "setspecial", "getdynamic", "setdy
namic", "getinstancevariable", "setinstancevariable", "getclassvariable", "setcl
assvariable", "getconstant", "setconstant", "getglobal", "setglobal", "putnil",
"putself", "putobject", "putspecialobject", "putiseq", "putstring", "concatstrin
gs", "tostring", "toregexp", "newarray", "duparray", "expandarray", "concatarray
", "splatarray", "checkincludearray", "newhash", "newrange", "pop", "dup", "dupn
", "swap", "reput", "topn", "setn", "adjuststack", "defined", "trace", "definecl
ass", "send", "invokesuper", "invokeblock", "leave", "finish", "throw", "jump",
"branchif", "branchunless", "getinlinecache", "onceinlinecache", "setinlinecache
", "opt_case_dispatch", "opt_checkenv", "opt_plus", "opt_minus", "opt_mult", "op
t_div", "opt_mod", "opt_eq", "opt_neq", "opt_lt", "opt_le", "opt_gt", "opt_ge",
"opt_ltlt", "opt_aref", "opt_aset", "opt_length", "opt_succ", "opt_not", "opt_re
gexpmatch1", "opt_regexpmatch2", "opt_call_c_function", "bitblt", "answer"]

值得注意的还有一个方法:
  1. p RubyVM::InstructionSequence.compile_option
复制代码
输出:

{:inline_const_cache=>true, :peephole_optimization=>true, :tailcall_optimization
=>false, :specialized_instruction=>true, :operands_unification=>false, :instruct
ions_unification=>false, :stack_caching=>false, :debug_level=>0}

这其实是 YARV 使用的一些优化方法。就目前来说,有很多高级优化技术,YARV 都还没有使用上,可见 Ruby 的性能提升还是有很大空间的。
作者: 小幽的马甲    时间: 2010-10-19 21:27
本帖最后由 小幽的马甲 于 2010-10-19 22:09 编辑

简单地说下Symbol类。
Symbol类似一个静态的String。类似于:aaa之类的字面值都是Symbol,可以调用String#to_sym或String#intern把不包含空字符的字符串转化为符号,调用Symbol#to_s或Symbol#id2name把符号转化为字符串。
作为字面值使用时,冒号后面跟双引号和单引号结果是不同的,要注意

  1. a = "abc"
  2. p :"#{a}" #=> :abc
  3. p :'#{a}' #=> :'#{a}'
复制代码
Symbol同样也有%号表示法:%s。

  1. p %s{aaa}         #=>:aaa
  2. p %s{aaa bbb}     #=>:"aaa bbb"
  3. p %s{aaa\n}       #=>:"aaa\\n"
复制代码
Symbol类有一个类方法:Symbol.all_symbols,用于以数组的形式返回所有已定义的符号对象。

Symbol一个很重要的性质是,字面值一样的Symbol都是同一个Object

  1. str1 = "aaa"
  2. str2 = "aaa"
  3. p str1.equal? str2   #=>false
  4. sym1 = :aaa
  5. sym2 = :aaa
  6. p sym1.equal? sym2   #=>true
复制代码
这使得它在执行比较的时候比String效率高(Symbol继承了Object的self==other,String则是重新定义,让两个字符串逐字比较),所以当String仅仅用来作为一个标志的时候可以考虑替换为Symbol。

  1. def move(direction)
  2.   @x += 1 if direction == :right
  3.   @x -= 1 if direction == :left
  4.   @y += 1 if direction == :up
  5.   @y -= 1 if direction == :down
  6. end
复制代码
(Ruby1.9相关)
在Ruby1.9里,Hash可以这么定义:
  1. {a:"aaa",b:"bbb"}
复制代码
它等价于
  1. {:a => "aaa", :b => "bbb"}
复制代码
在Ruby1.9中,Symbol类加入了大量的实例方法,大部分都是String类的实例方法,如capitalize、downcase、encoding
  1. p :aaa.upcase  #=>:AAA
复制代码
25楼的67在说明&的应用时给Symbol定义了to_proc方法,在Ruby1.9就不用这样,Symbol自带to_proc
  1. words = %w(hello kitty not world)
  2. list = words.map(&:capitalize)
复制代码
ok,得到了第一个字母大写的字符串数组了

顺便说一句,默认RGSS看似没怎么用Symbol,但你不知不觉地已经用了很多了。
  1.   attr_accessor :next_scene               # 切换待机中的画面 (文字列)
复制代码

作者: 小幽的马甲    时间: 2010-10-20 19:04
本帖最后由 小幽的马甲 于 2010-10-20 20:21 编辑

基本位运算
在Matrix67Blog看到Pascal版本,觉得很好,于是转为Ruby语言版

顺便说句,Ruby和Pascal的位运算优先级是不同的(和C++大致相同不过似乎也有点小区别),Ruby运算优先级参考本帖29楼
作者: 小幽的马甲    时间: 2010-10-21 20:50
几个神奇的位运算应用,问题转自Matrix67-Blog,代码已翻译为Ruby,限于篇幅不转代码的说明了,原文里有(其实用纸和笔画一画很容易想通{:nm_6:} )

二进制中的1有奇数个还是偶数个
我们可以用下面的代码来计算一个32位整数的二进制中1的个数的奇偶性,当输入数据的二进制表示里有偶数个数字1时程序输出0,有奇数个则输出1。例如,1314520的二进制101000000111011011000中有9个1,则x=1314520时程序输出1。
  1. def calc1(x)
  2.   5.times{|i| x ^= x >> (1 << i)}
  3.   return x & 1
  4. end
复制代码
计算二进制中的1的个数
同样假设x是一个32位整数。经过下面五次赋值后,x的值就是原数的二进制表示中数字1的个数。比如,初始时x为1314520,那么最后x就变成了9,它表示1314520的二进制中有9个1。

  1. def calc2(x)
  2.   x = (x & 0x55555555) + (x >> 1 & 0x55555555)
  3.   x = (x & 0x33333333) + (x >> 2 & 0x33333333)
  4.   x = (x & 0x0F0F0F0F) + (x >> 4 & 0x0F0F0F0F)
  5.   x = (x & 0x00FF00FF) + (x >> 8 & 0x00FF00FF)
  6.   x = (x & 0x0000FFFF) + (x >> 16 & 0x0000FFFF)
  7.   return x
  8. end
复制代码
只用位运算来取绝对值
x仍为一个32位整数。

  1. def calc3(x)
  2.   return (x ^ (~ (x >> 31) + 1)) + (x >> 31)
  3. end
复制代码
二进制逆序
    下面的程序读入一个32位整数并输出它的二进制倒序后所表示的数。
    输入: 1314520    (二进制为00000000000101000000111011011000)
    输出: 460335104  (二进制为00011011011100000010100000000000)

  1. def calc4(x)
  2.   x = (x & 0x55555555) <<  1 | (x & 0xAAAAAAAA) >>  1;
  3.   x = (x & 0x33333333) <<  2 | (x & 0xCCCCCCCC) >>  2;
  4.   x = (x & 0x0F0F0F0F) <<  4 | (x & 0xF0F0F0F0) >>  4;
  5.   x = (x & 0x00FF00FF) <<  8 | (x & 0xFF00FF00) >>  8;
  6.   x = (x & 0x0000FFFF) << 16 | (x & 0xFFFF0000) >> 16;
  7.   return x
  8. end
复制代码

作者: 小幽的马甲    时间: 2010-10-22 18:09
本帖最后由 小幽的马甲 于 2010-10-22 22:39 编辑

用一段简单的代码概括Ruby异常类要点

  1. class Test
  2.   def initialize
  3.     err = nil
  4.     begin
  5.       return unless err.nil?
  6.       raise ArgumentError, "Test"
  7.     rescue IOError
  8.       print "IOError"
  9.     rescue StandardError,ScriptError => err
  10.       print "Error:#{$!}"
  11.       retry
  12.     rescue ArgumentError
  13.       print "ArgumenError"
  14.     rescue
  15.       print "Error"
  16.     else
  17.       print "No Errors!"
  18.     ensure
  19.       print "err = #{err}\n$! = #{$!}"
  20.     end
  21.   end
  22. end
  23. Test.new
复制代码
输出:

  1. Error:Test
  2. err = Test
  3. $! =
复制代码
异常这一块难度不大,知识点不少,所以用这一段概括大部分知识点,没有接触过异常的同学看后也能明白了吧…?
作者: 小幽的马甲    时间: 2010-10-23 16:01
辨析:Kernel#eval、BasicObject#instance_eval、Module#module_eval(Module#class_eval)

(Kernel#)eval把给定的字符串作为代码运算并返回结果。和其它两个不同的是,eval不能传递块。binding可以作为eval的第二个参数传入。

  1. def a
  2.   b = 5
  3.   return binding
  4. end
  5. eval("p b",a) #=>"5"
复制代码
(Basic)Object#instance_eval在一个实例的上下文中eval字符串或块(可以访问类内的方法、变量等)。所有对类的方法或变量的操作只在这个实例中生效,而不会影响类的其它实例。

  1. a = String.new
  2. b = String.new
  3. a.instance_eval{
  4. def foo
  5.   p "foo"
  6. end
  7. }
  8. a.foo                         #=>"foo"
  9. b.foo rescue p nil            #=>nil
  10. p String.method_defined? :foo #=>false
复制代码
Module#module_eval(class_eval)实在模块或类的上下文中eval字符串或块。定义的东西会影响所有实例,主要用于给类或模块定义新的方法。
  1. a = String.new
  2. b = String.new
  3. String.class_eval{
  4. def bar
  5.   p "bar"
  6. end
  7. }
  8. a.bar                         #=>"bar"
  9. b.bar                         #=>"bar"
  10. p String.method_defined? :bar #=>true
复制代码

作者: DeathKing    时间: 2010-10-24 08:12
本帖最后由 DeathKing 于 2010-10-24 12:23 编辑

欢欢乐乐来循环

经典的for语句:
  1. for i in 0..100
  2.   p "i count #{i}"
  3. end
复制代码
换个马甲如何?
  1. (0..100).each do |i|
  2.   p "i count #{i}"
  3. end
复制代码
原来是重复101次!
  1. 101.times do |i|
  2.   p "i count #{i}"
  3. end
复制代码
想想,是从0数到100:
  1. 0.upto(100) do |i|
  2.   p "i count #{i}"
  3. end
复制代码
我愿意倒着数:
  1. 100.downto(0) do |i|
  2.   p "i count #{i}"
  3. end
复制代码
还没数到100就别停下来
  1. i = 0
  2. while i <= 100
  3.   p "i count #{i}"
  4.   i += 1
  5. end
复制代码
似乎这个更清晰:
  1. i = 0
  2. until i == 101
  3.   p "i count #{i}"
  4.   i += 1
  5. end
复制代码
他们都是循环的快速通道:
for, while, until

Range#each
Integer#times
Integer#upto
Integer#downto
String#upto
String#downto

作者: zh99998    时间: 2010-10-25 16:04
又自己抓出来一个ruby1.9小细节
p [1,2,3].to_s #=> "[1,2,3]"  in 1.9
p [1,2,3].to_s #=> "123"  in 1.8

数组的.to_s变成了以人类可读形式输出
作者: zh99998    时间: 2010-10-29 15:37
标题: 条件编译
原文:http://rpg.blue/thread-159531-1-2.html

对于一些不是很“动态”的条件分歧,例如对系统的判断等程序开始运行之后一般不会变的
if system_is_windows?
  def do
    #do_in_windows
  end
elsif system_is_linux?
  def do
    #do_in_linux
  end
end

由于ruby的一切都是运行时处理,而没有原生的预编译指令,所以这看起来不像预编译
但是想象一下RM的脚本架构(其他比较大的ruby工程也是像这样),在main之前,定义所有类和方法,这个过程可以近似的认为是编译
如果是做通用库,或者是需要大量执行的方法,效率会有一定提升(吗)
作者: 沉影不器    时间: 2010-10-30 19:56
提示: 作者被禁止或删除 内容自动屏蔽
作者: IamI    时间: 2010-10-31 21:16
今天没人?
Table数据的取值范围是-32767--32768。
作者: goahead    时间: 2010-11-1 11:34
提示: 作者被禁止或删除 内容自动屏蔽
作者: zh99998    时间: 2010-11-3 16:22
与c等语言不同,ruby的整数大小没有限制,超过特定值后会自动变成Bignum
作者: DeathKing    时间: 2010-11-7 08:26
本帖最后由 DeathKing 于 2010-11-7 08:27 编辑

像打开普通文件那样打开网页,open-uri是个好工具:
  1. require 'open-uri.rb'
  2. open("http://rpg.blue") do |f|
  3.   f.each_line do |line|
  4.     p line
  5.   end
  6. end
复制代码
为了获得更好的效果,最好先将KCODE设置为UTF-8
  1. $KCODE = 'u'
复制代码
[line]1[/line]
一些参考:
1,http://www.kuqin.com/rubycndocument/man/addlib/open-uri.html
2,http://fireflyman.javaeye.com/blog/671898

作者: yangff    时间: 2010-11-9 19:20
你要相信,Ruby可以像很多脚本语言一样抽插在各种软件当中……实现起来非常简单,只要编译一下……
作者: DeathKing    时间: 2010-11-12 18:19
本帖最后由 DeathKing 于 2010-11-12 18:19 编辑

ri,一个很好的帮手,不过需要安装了Ruby,因为他是有Dave Thomas(《Programming Ruby》的作者之一)设计的工具。
  1. Usage: ri.bat [options] [names...]
复制代码
查询一下Net::POP3如何使用,有点像是man?真是意外的详细
  1. C:\Documents and Settings\DeathKing>ri Net::POP3
  2. -------------------------------------------- Class: Net::POP3 < Protocol

  3. NET::POP3
  4. =========


  5. What is This Library?
  6. ---------------------

  7.      This library provides functionality for retrieving email via POP3,
  8.      the Post Office Protocol version 3. For details of POP3, see
  9.      [RFC1939] (http://www.ietf.org/rfc/rfc1939.txt).


  10. Examples
  11. --------

  12.      Retrieving Messages

  13.      This example retrieves messages from the server and deletes them on
  14.      the server.

  15.      Messages are written to files named 'inbox/1', 'inbox/2', ....
  16.      Replace 'pop.example.com' with your POP3 server address, and
  17.      'YourAccount' and 'YourPassword' with the appropriate account
  18.      details.

  19.          require 'net/pop'

  20.          pop = Net::POP3.new('pop.example.com')
  21.          pop.start('YourAccount', 'YourPassword')             # (1)
  22.          if pop.mails.empty?
  23.            puts 'No mail.'
  24.          else
  25.            i = 0
  26.            pop.each_mail do |m|   # or "pop.mails.each ..."   # (2)
  27.              File.open("inbox/#{i}", 'w') do |f|
  28.                f.write m.pop
  29.              end
  30.              m.delete
  31.              i += 1
  32.            end
  33.            puts "#{pop.mails.size} mails popped."
  34.          end
  35.          pop.finish                                           # (3)

  36.      1.  Call Net::POP3#start and start POP session.

  37.      2.  Access messages by using POP3#each_mail and/or POP3#mails.

  38.      3.  Close POP session by calling POP3#finish or use the block form
  39.          of #start.

  40.      Shortened Code

  41.      The example above is very verbose. You can shorten the code by
  42.      using some utility methods. First, the block form of
  43.      Net::POP3.start can be used instead of POP3.new, POP3#start and
  44.      POP3#finish.

  45.          require 'net/pop'

  46.          Net::POP3.start('pop.example.com', 110,
  47.                          'YourAccount', 'YourPassword') do |pop|
  48.            if pop.mails.empty?
  49.              puts 'No mail.'
  50.            else
  51.              i = 0
  52.              pop.each_mail do |m|   # or "pop.mails.each ..."
  53.                File.open("inbox/#{i}", 'w') do |f|
  54.                  f.write m.pop
  55.                end
  56.                m.delete
  57.                i += 1
  58.              end
  59.              puts "#{pop.mails.size} mails popped."
  60.            end
  61.          end

  62.      POP3#delete_all is an alternative for #each_mail and #delete.

  63.          require 'net/pop'

  64.          Net::POP3.start('pop.example.com', 110,
  65.                          'YourAccount', 'YourPassword') do |pop|
  66.            if pop.mails.empty?
  67.              puts 'No mail.'
  68.            else
  69.              i = 1
  70.              pop.delete_all do |m|
  71.                File.open("inbox/#{i}", 'w') do |f|
  72.                  f.write m.pop
  73.                end
  74.                i += 1
  75.              end
  76.            end
  77.          end

  78.      And here is an even shorter example.

  79.          require 'net/pop'

  80.          i = 0
  81.          Net::POP3.delete_all('pop.example.com', 110,
  82.                               'YourAccount', 'YourPassword') do |m|
  83.            File.open("inbox/#{i}", 'w') do |f|
  84.              f.write m.pop
  85.            end
  86.            i += 1
  87.          end

  88.      Memory Space Issues

  89.      All the examples above get each message as one big string. This
  90.      example avoids this.

  91.          require 'net/pop'

  92.          i = 1
  93.          Net::POP3.delete_all('pop.example.com', 110,
  94.                               'YourAccount', 'YourPassword') do |m|
  95.            File.open("inbox/#{i}", 'w') do |f|
  96.              m.pop do |chunk|    # get a message little by little.
  97.                f.write chunk
  98.              end
  99.              i += 1
  100.            end
  101.          end

  102.      Using APOP

  103.      The net/pop library supports APOP authentication. To use APOP, use
  104.      the Net::APOP class instead of the Net::POP3 class. You can use the
  105.      utility method, Net::POP3.APOP(). For example:

  106.          require 'net/pop'

  107.          # Use APOP authentication if $isapop == true
  108.          pop = Net::POP3.APOP($is_apop).new('apop.example.com', 110)
  109.          pop.start(YourAccount', 'YourPassword') do |pop|
  110.            # Rest of the code is the same.
  111.          end

  112.      Fetch Only Selected Mail Using 'UIDL' POP Command

  113.      If your POP server provides UIDL functionality, you can grab only
  114.      selected mails from the POP server. e.g.

  115.          def need_pop?( id )
  116.            # determine if we need pop this mail...
  117.          end

  118.          Net::POP3.start('pop.example.com', 110,
  119.                          'Your account', 'Your password') do |pop|
  120.            pop.mails.select { |m| need_pop?(m.unique_id) }.each do |m|
  121.              do_something(m.pop)
  122.            end
  123.          end

  124.      The POPMail#unique_id() method returns the unique-id of the message
  125.      as a String. Normally the unique-id is a hash of the message.

  126. ------------------------------------------------------------------------


  127. Constants:
  128. ----------

  129.      Revision = %q$Revision: 19776 $.split[1]


  130. Attributes:
  131. -----------

  132.      address (R):
  133.           The address to connect to.

  134.      open_timeout (RW):
  135.           Seconds to wait until a connection is opened. If the POP3
  136.           object cannot open a connection within this time, it raises a
  137.           TimeoutError exception.

  138.      read_timeout (R):
  139.           Seconds to wait until reading one block (by one read(1) call).
  140.           If the POP3 object cannot complete a read() within this time,
  141.           it raises a TimeoutError exception.



  142. Class methods:
  143. --------------

  144.      APOP, auth_only, certs, create_ssl_params, default_pop3_port,
  145.      default_pop3s_port, default_port, delete_all, disable_ssl,
  146.      enable_ssl, foreach, new, ssl_params, start, use_ssl?, verify


  147. Instance methods:
  148. -----------------

  149.      active?, apop?, auth_only, command, delete_all, disable_ssl,
  150.      do_finish, do_start, each, each_mail, enable_ssl, finish, inspect,
  151.      logging, mails, n_bytes, n_mails, on_connect, port, read_timeout=,
  152.      reset, set_debug_output, start, started?, use_ssl?
复制代码

作者: DeathKing    时间: 2010-11-16 23:17
本帖最后由 DeathKing 于 2010-11-16 23:18 编辑

一些小问题
  1. def f(n)
  2.   if n == 1
  3.     return 1
  4.   else
  5.     return n * f(n - 1)
  6.   end
  7. end

  8. f(1_000_000)
复制代码
看上去是计算1,000,000的阶乘,实际呢?这种递归运算当到达一定的堆栈深度时会被停止,引发stack level too deep异常而中止。
  1. SystemStackError: stack level too deep
  2.         from (irb):21:in `f'
  3. ... 7656 levels...
复制代码
进行了7656层,在1.8.6中,我的机器只能进行1479层。所以要合理使用递归,关注堆栈深度。

[line]1[/line]
只做抛砖引玉,请讨论更深入的问题


作者: zh99998    时间: 2010-11-17 16:19
逻辑表达式

Ruby把nil和false认为逻辑假,其余任何对象认为逻辑真

if 0
  print "0 is true"
else
  print "0 is false"
end

这在其他语言里一般会得到 0 is false,但是在Ruby里是 0 is true

可以利用这个特性来做一些有趣的简写

while line = gets
  print line
end

会把标准输入一行行打印出来,直到文件尾


由于if不一定必须为true和false,而是可以使用任何对象,所以逻辑运算符也不会强制返回逻辑值,而是返回第一个决定逻辑值的对象


true and nil #=>nil
1 and "a" #=>"a"
false and true #=> false
false or nil #=> nil
&& 和 || 也有这一特性

比较常用的有个||=运算符,可以用来代替 a = x if a这样的语句

hash = {a: "b"}
hash[:a] ||= "c"
在想修改hash或数组的值,但是不确定那个键是否存在,并不想因此额外增加长度时使用
作者: 苏小脉    时间: 2010-11-18 06:56
本帖最后由 苏小脉 于 2010-11-18 06:57 编辑

The great ruby shootout:
http://programmingzen.com/2010/0 ... shootout-july-2010/

这是一个由来自 IBM 的软件工程师 Antonio Cangiano 执行的 benchmark,其结果展示了不同 Ruby 的实现在 Linux 和 Windows 上的不同性能。

官方版的 Ruby 实现(CRuby)开发环境是 Linux,优化时针对 Linux 的多,针对 Windows NT 的少,再加上编译器优化性能的差异(GCC <=> MSVC),使得 Linux 下的 CRuby 性能整体高于 Windows。

在 Linux 下,平均最快的实现似乎是 基于 YARV 虚拟机的 Ruby 1.9.2(即 CRuby 的最新稳定 Release),基于 Sun JVM 的 JRuby 紧随其后;Windows 下,JRuby 的性能胜过了 Ruby 1.9.2。

MagLev 和 Rubinius 是这前两名巨头最大的竞争对手,现在仍在快速发展。特别值得一提的是 Rubinius,其最新的 1.1 版本在 JIT 优化和 GIL 性能上又有了提升,相信她在下一次 shootout 会有杰出的表现。

东风吹,战鼓擂,当今世界谁怕谁。革命仍未成功,阶级斗争还在继续。
作者: DeathKing    时间: 2010-11-28 13:17
( Dave Thomas && Matz ) @ Ruby Conf 2010

http://player.youku.com/player.php/sid/XMjI1NzU2MjI0/v.swf


http://player.youku.com/player.php/sid/XMjI1Nzc2MzIw/v.swf



DeathKing于2010-12-11 17:30补充以下内容:
给新手的String类参考手册:http://rpg.blue/thread-162871-1-1.html
作者: 苏小脉    时间: 2010-12-23 06:33
本帖最后由 苏小脉 于 2010-12-24 03:56 编辑

字符串扣留

 先从一个 Ruby 实例看起:

p "hello".object_id, "hello".object_id

  object_id 可以获取一个对象的唯一标识,而这行代码打印出了不同的 ID,说明两次引用的字符串字面值生成了两个不同的字符串对象,但它们的内容是一致的。这在面向对象语言中是一个普遍的现象,在某些场合下可能会引起以下问题:

   1. 当频繁使用 Ruby 的字面值指定某个字符串时,每次通过 Ruby 的字面值指定的字符串都会生成一个新的对象,长此以往就会产生大量的内容相同的字符串对象。这种空间的浪费意味着不必要的数据占用了更多的 Cache 和内存,减少了其它资源利用 Cache 的机会,也同时增加了内存分配和回收的复杂度;
   2. 我们经常需要比较字符串是否相等(如散列表的键值比较),而无论是什么语言,底层的实现方法也无非是调用 memcmp、strcmp 之类的函数来比较内存,理论上这个过程需要 O(n) 的时间(n = 字符串长度),而如果这样的比较需要频繁进行,那代价也是不容忽视的。

  当今不少高级语言中,都内置一种被称为“扣留”(Intern)的机制,它解决了这一难题。它的思想是:使用一个全局的字符串常量池来管理字符串,每次准备分配新的字符串对象之前,程序都会先检查内容相同的字符串对象是否已经被“扣留”在了常量池中,若是,则不分配新的对象,直接引用常量池中的对象;若不是则分配新的对象,并“扣留”在常量池中。这当然增加了字符串对象构造时的时间,但却解决了上述两种情况下的空间(没有产生字符串的克隆)和时间(逐字节比较字符串变为了指针相等的判断)效率问题。

  这个机制最早由 Lisp 用来管理符号,后来也被 Python(intern(string))、Java(String#intern)、.NET(String.intern) 等语言继承,用来实现字符串的扣留。Ruby 通过符号表实现了符号对象的扣留机制,但内置的字符串对象并没有扣留。Ruby 的 String#intern 方法看似扣留,实则是把一个已有的字符串对象转换为符号对象。使用这个方法并不会节省内存,只能起到简化字符串比较的时间复杂度的作用。所以,在 Ruby 中,操作常量的字符串并处于上述两种情况时应该尽量使用符号而不是字符串。Ruby 1.9 的新 Hash 字面值支持一种更简洁的符号键语法糖,其设计目的就是为了推荐用户使用符号而不是字符串。

http://szsu.wordpress.com/2010/09/21/string_intern/
作者: 紫苏    时间: 2010-12-24 03:57
正则表达式字面值的对象构建

  之前曾谈到了字符串扣留的概念,那么对于 Ruby 的正则表达式 Regexp 类型来说,是否也有类似的常量池机制呢?

  (注:以下输出的具体数字随环境而变,重要的是其前后的相等性)

  我们可以做一个简单的测试:

  1. 2.times { p "foo".object_id }
  2. 2.times { p :foo.object_id }
  3. 2.times { p /foo/.object_id }
复制代码
  输出:
5712492
5712432
146408
146408
5713608
5713608

  根据输出结果,我们似乎可以得出结论, Regexp 类型也有类似字符串扣留的机制。然而细心的人会进一步测试这段代码:

  1. 2.times { p Regexp.new(/foo/).object_id }
复制代码
  输出了两个不同的数字。这和之前的测试难道不是矛盾的吗?当然不是,这实际上和 Ruby 的语法分析器有关。

  Ruby 支持正则表达式字面值(/…/)。对于官方的 Ruby 实现来说,其语法分析器会在接受一个正则表达式的单词(token)时生成该正则表达式对象。也就是说,字面值形式的正则表达式是在语法分析时生成的,也可以宏观地看作是在编译时生成的。因此,在运行时,虽然会多次调用 /foo/.object_id,但由于语法分析器只看到了一个正则表达式字面值,其接收者永远都是一个对象。

  证明:

  1. ObjectSpace.each_object(Regexp) { |r| p r }
  2. /foo/
复制代码
  第一行是打印出当前的 Ruby 实例中所有 Regexp 对象,而这时解释器还没执行到第二行的字面值。实际的输出中,也确实包含 /foo/ 这个对象。

  如果语法分析器看到两个正则表达式字面值,自然也就会生成两个 Regexp 对象了:

  1. p /foo/.object_id, /foo/.object_id
复制代码
  输出:
5713896
5713812


http://szsu.wordpress.com/2010/12/22/regex_literal/
作者: 苏小脉    时间: 2010-12-25 23:56
Ruby 1.9.2-p136 今天刚发布了,这是 Ruby 1.9.2 的第二个发行版,它修复了很多已知的 1.9.2-p0 的 BUG。

Change log:http://svn.ruby-lang.org/repos/ruby/tags/v1_9_2_136/ChangeLog
下载:
http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.2-p...
  SIZE:   8819324 bytes
  MD5:    52958d35d1b437f5d9d225690de94c13
  SHA256:
33092509aad118f07f0483a3db1d4c5adaccf4bb0324cd43f44e3bd3dd1858cb

* http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.2-p136.tar.gz
  SIZE:   11155066 bytes
  MD5:    6e17b200b907244478582b7d06cd512e
  SHA256:
c4314df44f3ab81230685fb51c296ce21034f4c719e2fcc0baba221d19f28746

* http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.2-p136.zip
  SIZE:   12566581 bytes
  MD5:    f400021058e886786ded510a9f45b2c6
  SHA256:
84ffc047b29032ba848dbbf50d3302de7ac732db1448e57303c27ad4b47c2c5b


http://www.ruby-forum.com/topic/732814#new
作者: 紫苏    时间: 2010-12-26 00:14
想在第一时间获得 Ruby 官方实现的最新 tag 么,从官方的 SVN checkout 是不二的选择!

trunk
      $ svn co http://svn.ruby-lang.org/repos/ruby/trunk ruby
ruby_1_8 branch
      $ svn co http://svn.ruby-lang.org/repos/ruby/branches/ruby_1_8
ruby_1_8_5 branch
      $ svn co http://svn.ruby-lang.org/repos/ruby/branches/ruby_1_8_5

主干上的是 1.9.2 的开发,另外还有 1.8 两个版本的分支。如果你只想浏览,不想 checkout,可以用这个 ViewVC 的接口:
http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/?root=ruby
没事可以读一读 Ruby 源代码什么的。
作者: 苏小脉    时间: 2010-12-27 00:08
Ruby 1.8 的 Kernel#require 可以接受一个绝对路径,如果不是绝对路径,就会在 $: 保存的搜索路径中进行搜索,但这默认不包括当前工作路径。

Ruby 1.9 多了一个 Kernel#require_relative,接受一个相对于当前源文件的路径,这样就不用显示地去把当前工作路径添加到 $: 中或显示地指定 `.' 了。要注意的是在 irb 中这个函数是无法使用的,因为 irb 并没有一个明确的源文件定义。
作者: 紫苏    时间: 2011-1-8 14:56
Symbols 和 Fixnum 类型的实例是不能拥有单例类和单例方法的。

  1. foo = 0
  2. def foo.bar; end # => can't define singleton method "bar" for Fixnum (TypeError)
复制代码
至于有这个限制的原因,窃以为是由于直接值和单例方法的实现导致的——
创建单例方法有一个先决条件,就是必须要先有一个单例类对象,之后才能在这个类上下文中进行方法定义。普通的对象实例都可以并默认拥有自己的单例类对象,然而 Fixnum 和 Symbols 有两个特性:可以有多个实例,且每个实例都是直接值。他们由 object_id 抽象地表示着,并不是物理存在于 Ruby 层的对象,所以并没有一个高效的方法来为每一个“抽象的”对象定义单例类。TrueClass、FalseClass、NilClass 类型则完全不同,虽然他们的实例引用也是直接值,但由于他们各自在整个程序的生命周期中都只有一个实例,所以他们的单例类就可以是这三个类本身。

  1. p true.singleton_class
  2. p false.singleton_class
  3. p nil.singleton_class

  4. obj = Object.new
  5. p obj
  6. p obj.singleton_class
复制代码
输出:
TrueClass
FalseClass
NilClass
#<Object:0x471c18>
#<Class:#<Object:0x471c18>>

而:
  1. p (:foo).singleton_class
复制代码
则:
in `singleton_class': can't define singleton (TypeError)

作者: 苏小脉    时间: 2011-1-9 02:37
本帖最后由 苏小脉 于 2011-1-9 02:48 编辑

Ruby 和大部分 OO 语言一样,都是在语言、语法层仅支持单一方法分派的语言。单一分派(single dispatch)是在通过迟(动态)绑定实现多态时的一种思路,它依靠接收者的运行时类型动态地分派应该调用的方法。

Smalltalk、Python 以及 Ruby 使用的都是一种实现方式,那就是通过方法分派函数(dispatch function)和分派表(dispatch table)进行消息到方法的映射。每个类型在定义时就内置了这样的分派函数和分派表,后者是一个消息(符号)到实际方法的映射表,而前者接受一个输入符号,然后通过分派表映射到正确的方法(其间包含处理派生类继承的方法和模块 mixin 后的方法等比较复杂的过程)。当我们在 Ruby 中以 obj.method 的形式调用一个方法时,按照 Smalltalk 的程序语言概念,就是向 obj 这个对象发送了一个消息,传递了一个 :method 符号,而作为接收者的 obj 会通过分派函数在分派表中找到对应的方法。这整个过程就是所谓的动态方法分派(dynamic method dispatch)。

比如:基类和派生类有同名方法,但在实际调用时,随着接收者的不同,实际调用的方法也不同,当接收者是基类实例时调用基类实例方法,当接收者是派生类实例时调用派生类实力方法。关于这个的更多信息,还可以参考本主题早期的一篇关于迟绑定的回帖。

既然 Ruby 使用的分派模式唤作单一分派,那自然还有多重分派(multiple dispatch)。Common-Lisp 是一个众所周知的最早支持多重分派的语言。多重分派通常是指在动态判断接收者类型的同时也判断方法参数的动态类型的分派模式。更有甚者,会判断参数的值,返回类型,或返回值。

要注意这里是方法参数的动态类型(运行时类型),而不是静态类型,这是关键。对于支持静态类型的语言来说,由于方法参数的类型可以在编译时决定,所以他完全可以做静态分派,也就是静态绑定(早绑定)。静态类型语言中的方法重载就是这样的一个概念——静态类型的参数在编译时就可以确定,所以方法的分派也是静态的。

比如,C++ 的方法重载,就是通过不同的静态类型的参数实现的,这个过程不属于动态分派,自然也就不能称之为多重分派。实际上,大多数 C++ 编译器都会给重载的方法/函数进行一个所谓的方法名/函数名装饰(function name decoration)的处理,所以在链接的时候,实际参与链接使用的正式函数名并不是你在源代码中敲下的函数名,而会有一些别的奇怪的符号,这个想必自己写过 C++ 共享库的朋友都深有体会。动态多重分派的严格定义,是指方法名不变,根据接收者和参数类型(甚至参数值、返回类型、返回值)判断应该调用的方法的一种多态。当然,由于 C++ 本身有虚函数的机制,所以还是支持动态分派的,只不过不支持多重分派罢了。

像后来的 Java 这样的静态类型语言,看起来也是支持方法重载,实际上也和 C++ 一样,只是单一分派罢了。C# 是个怪胎,C# 4.0 同时支持静态类型和动态类型,这使得多重分派在 4.0 中得到了支持。Python、Ruby 都是没有静态类型的语言,所以不可能像 C++ 那样去根据静态类型在编译时做函数名装饰,也就不可能有静态分派。因此,传统意义上的方法重载在这样的纯动态类型语言中是不存在的。由于不能重载,相同名称的方法就只有一个定义,所以在语言层、语法层是不能实现多重分派的。要想在仅支持单一分派的语言中使用多重分派,只能在高层模拟进行,比如 Ruby 可以通过 Object#class 去判断对象的运行时类(型),然后根据类型的不同分别处理。很多第三方的扩展库就是这么做的。

  1. def foo(bar)
  2.         case bar
  3.         when String
  4.                 p 'bar'
  5.         when Symbol
  6.                 p :bar
  7.         end
  8. end

  9. foo :foo
  10. foo 'foo'
复制代码
输出:
:bar
"bar"

作者: 紫苏    时间: 2011-1-15 13:15
case 语句可以用来做对象运行时类型的分歧:

  1. #coding: GBK

  2. foo = rand >= 0.5 ? 'foo' : :foo

  3. case foo
  4. when String
  5.     p '字符串'
  6. when Symbol
  7.     p '符号'
  8. end
复制代码
但经过测试后,你会发现 `foo == String || foo == Symbol' 从来都是 false。实际上,case 在判断相等性时使用的是 `===',而不是 `=='。如果并不了解这个事实,你可能会在使用 case 的时候习惯性、显式地去引用 `foo' 的类型:

  1. case foo.class
  2. when String
  3.     # ...
  4. when Symbol
  5.     # ...   
  6. end
复制代码
接着你就会发现,这段 case 没有产生预期的效果。
这是因为 Class 覆盖了基类 Object#=== 方法,而 Class#===(obj) 返回的是 `self == obj.class'。case 语句会让 when 后面的对象成为接收者,并发送调用 `===' 方法的消息给它,类似 `String === foo' 的形式。这个表达式展开其实就是 `String == foo.class',所以起到了预期的判断效果。正因如此,`String === String' 这样的表达式反而会返回 false。,而这和在 case 后面引用 foo 的类型是一个道理,因为 foo.class = { String, Symbols }。
作者: 苏小脉    时间: 2011-1-16 04:09
你可能听说过“解释性语言”这个词,它意味着一种间接被软件解释器程序执行(解释)的语言;与之相对的是“编译性语言”,即可以直接被翻译为机器码并交给 CPU 执行的语言(实际上,CPU 也可以被看作一种机器指令的解释器,只不过是硬件集成的)。然而,无论是“解释”或是“编译”,都并非是语言本质上的特性,而只是实现语言的方式。理论上来说,任何语言都同时可以被编译和解释。所以,“解释性语言”、“编译性语言”在实践时通常是指“频繁被解释的语言”、“频繁被编译的语言”。

早期的程序语言,要么就是被编译,要么就是被解释,所以他们只需要编译器或解释器的其中一个。比如,Shell 脚本就总是由命令行解释器(软件)来执行,而 C 程序总是由 C 编译器一次性翻译为机器码。随着计算机科学的不断发展,这种单调性也逐渐被淘汰,出现了很多使用混合模式的语言实现——同时拥有解释性和编译性。

官方的 Ruby 实现(CRuby)在 1.9 版本之前还没有一个虚拟机,所以它只有一个被称为 MRI(Matz’s Ruby Interpreter)的解释器。MRI 是一个纯解释性质的 AST walker(AST = Abstract Syntax Tree),硬直翻译成中文就是“抽象语法树步行器”,意即是在表示了整个程序的抽象语法树上行走并求值(之所以说“抽象”,是因为这个语法树并不一定非得有一个物理的树结构)。

到了 CRuby 1.9 版本时,出现了 YARV 这个 Ruby 虚拟机,“解释性”再也不是 CRuby 的唯一特性。在执行 Ruby 程序时,YARV 会首先将 Ruby 代码编译为 YARV 虚拟机器指令,这是一种中间代码,和 JVM 的字节码,CLR 的 托管代码类似。之后,YARV 再在这一系列 YARV 指令上进行动态的解释。所以 YARV 即是编译器,也是解释器。它进行了一种被称为 预编译(AOT,Ahead of Time Compilation)的处理,使得优化代码的空间得到了提升。

同理,同样是基于虚拟机之上的 JRuby 和 IronRuby 等实现自然也都会先对代码进行编译,产生一种中间代码。当然,他们还有别的模式,比如下文提到的 JIT 编译。

为了进一步强调第一段黑体文字所描述的事实,这里再举两个例子:

C 语言通常被描述为“编译性语言”,但实际上只是“频繁被编译的语言”。目前已经存在很多 C 解释器以及 C 虚拟机,能够让 C 被解释执行,或混合编译和解释执行。两个个比较有名的是 CINT 和 Ch。

Sun 的 Java 实现更是典型的混合模式实现。它的虚拟机(JVM)发展至今,已然十分健全,除了预编译器以外,还有即时编译器(JIT,Just-in-Time Compiler),充分应用了 LLVM 结构。即时编译机制会进行运行时程序概要分析,巧妙地找到所谓的被经常执行的被称为“热点”(hot-spot)的一段指令,在运行时重编译为本地机器码,并让 CPU 静态执行。基于 JVM 之上的 JRuby 也早已支持 JIT 了。
作者: 紫苏    时间: 2011-1-18 10:02
本帖最后由 紫苏 于 2011-1-18 10:11 编辑
  1. obj = Object.new
  2. class << obj
  3. end
复制代码
你知道这段代码中的 class 是什么吗?这段代码中的 class 定义的实际上就是 obj 这个对象的“单例类”。本帖主要是针对对 Ruby 单例类已经有了正确概念的朋友,否则可能不知所云。

Ruby 社区就 Ruby 的“单例类”这个术语的命名曾有过许多争议。至今,Ruby 的创造者 Matz 也没有宣布一个正式的、官方的的名称。在大部分书籍,包括《The Ruby Programming Language》中,都使用了“单例类”(Singleton class)这个术语,故而“单例类”也就成了 Ruby 的“单例类”概念的 de facto(约定俗成的)命名。除了“单例类”这个命名以外,主要还有另外两种命名:“元类”(Metaclass)和“特征类”(Eigenclass)。

“元类”实际上在更早的时候就出现了。Smalltalk、Java、C#、Python 中都有被称为“元类”的概念。然而,这些语言中的元类和 Ruby 的“单例类”颇有不同。传统意义上的元类,说白了,就是类的类,是用来描述一种类的类型,其实例就是一个类。这相当于 Ruby 中的 Class 类——所有对象的类的类型都是这个 Class 类的实例。这当然和“单例类”风马牛不相及,所以以“元类”来命名 Ruby “单例类”的概念并不合适。

“特征类”是一个很好的命名。这个“特征”和线性代数中“特征值”、“特征向量”的“特征”是一个意思。“特征值”、“特征向量”的名称是由德国数学家 David Hilbert (大卫·希尔伯特)在 1904 年使用并得以流传的,德语单词“Eigen”本意为“自己的”。Eigenclass,也就意味着“自己的类”,用于 Ruby 的“单例类”概念之上十分贴切,因为“单例类”实际上就是一个对象独有的类。

我个人认为使用最广泛的“单例类”这个名称并不好,因为这很容易和著名的单例类设计模式混淆。单例类设计模式是在设计类的时候保证其实例只有一个,而 Ruby 的“单例类”却是先有一个可有多个实例的类型,比如本帖开头的 Object 类型,而后对其某个实例进行“单例的类”(及其方法)的创建。这么一想,是不是觉得“特征类”更加贴切了(特别是对于专精于线性代数的你)?

Eigenclass 这个术语应该算是 Ruby 社员的发明创造。上文提到的“特征类”是我直接从线性代数中 eigenvalue、eigenvector 取来的,也并非是正式的、官方的翻译。就目前来说,使用 eigenclass 的英语原型是最不会引起歧义的。

关于 eigenclass 的命名,更详细的讨论参见:http://www.ruby-forum.com/topic/571597
作者: zh99998    时间: 2011-1-19 08:22
标题: 单例类
以下引用紫苏在73楼的发言
我个人认为使用最广泛的“单例类”这个名称并不好,因为这很容易和著名的单例类设计模式混淆。单例类设计模式是在设计类的时候保证其实例只有一个


所谓著名的单例类设计模式是指实例只有一个,例如内建的TrueClass FalseClass NilClass是单例类
在Ruby中单例类可以这么实现
  1. class Singleton
  2.   class << self
  3.     alias old_new new
  4.     def new(*args)
  5.       if @instance #可以用?:运算符或者逻辑或运算符||写成一句
  6.         @instance
  7.       else
  8.         @instance = old_new(*args)
  9.       end
  10.     end
  11.   end
  12. end
复制代码
而有另一些时候,我们希望的不是这个类只能有唯一实例,而是希望这个类的实例不会重复
例如有一个Game_Actor类(可能跟RM的不完全相同),有名字和其他属性,我们希望不会出现重名的两个角色
  1. class << Game_Actor #在RM默认脚本基础上,已经有了这个类
  2.   @all = {}
  3.   alias old_new new
  4.   def new(name, *args)
  5.     @all[name] || old_new(name, *args) #用逻辑或运算符的示例
  6.   end
  7. end
复制代码
这样第一次Game_Actor.new("拉尔夫")会生成一个新角色,第二次时直接返回第一次的
如果要用id标识唯一性,那么@all用数组即可
作者: DeathKing    时间: 2011-1-20 09:56
和其他的语言不一样,Ruby的常量(Constant)可以在初始化后随意改变其置。考虑下面一段代码。


将其保存为 test.rb 然后运行它。




在默认情况下运行它,程序对你说:嗨,老兄,你早已经初始化好了Apple这个常量,当然,他只是警告你,而程序继续运行。我们试试将安全等级设置低一点。ruby.exe 支持一个参数叫做安全等级,表示为 -W[0|1|2]。由于默认是第二等级,verbose,因此我们只需要测试0和1的情况。

当安全等级为0时,他就不发牢骚了。

RM的RGSS/RGSS2系统也不会发牢骚,这就是FSL脚本可以利用常量随时更改配置的原因了。



作者: 苏小脉    时间: 2011-1-21 12:27
本帖最后由 苏小脉 于 2011-1-21 12:28 编辑

这里有一篇 Yuki Sonoda (园田裕贵)的博文:http://yugui.jp/articles/846
其中介绍了三种隐式的上下文:self,klass,常量定义点(这个是我从英语直接翻译过来的)。

下面的概念是直接从该博文中摘抄下来的,如果你能很自在地读英语,那推荐看原文,其中包含了例子代码。

所谓 "klass",是指定义方法(不包括单例方法的定义)的默认(隐式)类,Yuki 称之为 "default definee"(默认被定义者)。比如,在任意处写下一个方法的定义(def foo; end),实际上是定义了某个类的实例方法,这个类就是这里说的 "klass"。Ruby 总是会保存这个类的一个引用,但官方的实现中并没有直接的方法获取这个类。

在 Ruby 顶层,定义一个方法,其 default definee 是 Object 类型。这个可以参考:http://szsu.wordpress.com/2009/11/07/top_level_object_kernel/

当你使用 class 的语法时,在 class ... end 内部会产生一个新的上下文,这时 self 和 default definee 都会指向这个类本身。所以在内部定义的方法就是该类的实例方法,这个大家都知道。

如果你已经在一个方法主体中,那么 self 是这个方法的接收者,而 default definee 则是语法上的方法定义外部的最近的那个类。
  1. class Klass
  2.     def foo
  3.         def bar
  4.         end
  5.     end
  6. end

  7. Klass.new.foo

  8. p Klass.instance_method :bar # => #<UnboundMethod: Klass#bar>
复制代码
这里 foo 也是 Klass 的实例方法。

当然,方法的定义还可以出现在其它地方,但无论在哪儿,都会有一个 default definee。比如 Yuki 给出的这个例子就是在处理方法参数默认值的时候定义的方法,其 default definee 仍然是语法上的外部的 Bar 类:
  1. class Bar; end  
  2. $bar = Bar.new  
  3. class Baz  
  4.   def $bar.hoge(a = (def fuga; end))  
  5.     def piyo; end  
  6.   end  
  7. end  
  8. $bar.hoge  
  9.   
  10. Baz.instance_method(:fuga)      #=> #<UnboundMethod: Baz#fuga>  
  11. Baz.instance_method(:piyo)      #=> #<UnboundMethod: Baz#piyo>  
  12.   
  13. $bar.method(:fuga)              # raises a NameError "undefined method `fuga' for class `Bar'"  
  14. $bar.method(:piyo)              # raises a NameError "undefined method `piyo' for class `Bar'"
复制代码
一句话:class 的定义改变了 default definee,而方法定义不会改变。

博文后半部分是关于 instance_eval 和 class_eval 在操纵 self 和 default definee 上的不同点,这个留待下一贴补完。

-----------------------------------------------------------

一些关于 Yuki Sonoda 的资料:
Yuki Sonoda,“园田裕贵”,是 CRuby 核心开发团队的成员之一,Ruby 社员大多称她为 “Yugui”。她目前负责管理 Ruby 1.9 的发布。
她的个人主页:http://yugui.jp/about/author
Linkedin 主页:http://jp.linkedin.com/in/yugui
Facebook 主页:http://www.facebook.com/yugui
Twitter 主页:http://twitter.com/#!/yugui
一张照片,左:Matz(松本行弘),中:Yuki,右:笹田耕一(Sasada Koichi),此人便是 Ruby 虚拟机 YARV 的作者。

作者: 紫苏    时间: 2011-1-22 14:33
(承接 76 楼)

instance_eval 和 class_eval 的区别,也是在于对 default definee 的处理上。由于 class_eval 是 Module 类的实例方法,所以下面进行测试使用的接收者是一个兼容 Module 类型的实例(其运行时类型是 Class 类型),否则 class_eval 这个方法压根儿没法用。另:module_eval 只是 class_eval 的一个别名,但如果按照 Ruby 编程的习俗,我们通常会对一个可能是 Module 类型的对象则使用 module_eval,而其余场合则使用 class_eval。

其实你需要记住的就一句话:在 instance_eval 的块内部,self 会指向 instance_eval 的接收者,而 default definee 会成为接收者的 eigenclass(关于 eigenclass 可以参考 73 楼);在 class_eval 的块内部,self 和 default definee 都会成为 class_eval 的接收者。
  1. class Klass
  2. end

  3. Klass.instance_eval do
  4.     def class_method
  5.         p :class_method
  6.     end
  7.     def self.another_class_method
  8.         p :another_class_method
  9.     end
  10. end

  11. Klass.class_eval do
  12.     def instance_method
  13.         p :instance_method
  14.     end
  15.     def self.yet_another_class_method
  16.         p :yet_another_class_method
  17.     end
  18. end

  19. k = Klass.new
  20. k.instance_method               # => :instance_method
  21. Klass.class_method              # => :class_method
  22. Klass.another_class_method      # => :another_class_method
  23. Klass.yet_another_class_method  # => :yet_another_class_method
复制代码
我们一步一步地来看。

首先,我们在 instance_eval 的块内部定义了一个普通方法 class_method,这是实例方法的定义方式。什么?你问我为什么把它命名为“类方法”(class_method)?别忘了之前的那句加粗的话。在 instance_eval 的块内部,default definee 会成为 instance_eval 的接收者的 eigenclass。此处,instance_eval 的接收者是 Klass,所以我们定义的 class_method 方法实际上就成为了 Klass 的 eigenclass 的实例方法,这用以前的术语来说,就是定义了 Klass 这个对象(Klass 是 Class 类型)的单例方法(singleton method)。而我们知道,Class 对象的单例方法,实际上就是传统意义上的“类方法”,也就类似于 Java、C# 中的类静态方法。所以此处的 class_method 就成为了 Klass 类的类方法,在最后的输出语句中,可以直接通过 Klass 这个 Class 对象来调用 class_method 方法,而不需要 Klass 的实例。

之后我们又定义了一个 another_class_method,这次是用定义单例方法的方式定义的,因为在方法名前多了一个接收者 self。之前说了 instance_eval 的块内部的 self 会指向 instance_eval 的接收者,也就是这里的 Klass,所以 self 实际上就是 Klass 这个 Class 对象。我们对 Klass 这个对象定义单例方法,那就相当于定义了 Klass 的 eigenclass 的实例方法,所以和上面定义 class_method 的上下文没有区别,都是 Klass 的 eigenclass,故而他们都是 Klass 的类方法。

接着我们进入了 class_eval,然后定义了一个 instance_method。再次回顾之前被加粗的那句话,我们就知道,class_eval 的块的内部的 default definee 会成为 class_eval 的接收者,在这里就是 Klass 本身,所以我们定义的 instance_method 就成为了 Klass 的实例方法。在最后的输出语句中,可以看到通过 Klass 的实例 k 调用 instance_method。

最后我们又通过单例方法定义的形式定义了一个 yet_another_class_method 方法。class_eval 的块内部的 self 仍然是指向 class_eval 的接收者,也就还是 Klass 本身,所以 self 仍然是指向 Klass。如此一来,这个方法定义又成了给 Klass 这个对象定义单例方法,所以和 class_method、another_class_method 一样都是 Klass 的类方法,故而命名为 yet_another_class_method。

所以,千万不要被 instance_eval 和 class_eval 的名字迷惑。在 Module#instance_eval 中定义的普通方法,反而会成为该 Module 的类方法;在 Module#class_eval 中定义的普通方法,反而会成为该 Module 的实例方法。
作者: DeathKing    时间: 2011-1-24 20:30
偷个小懒:
Ruby排序算法收集:http://www.javaeye.com/topic/746562

不过个人觉得,个个类的sort方法不然各具特色,也是最方便最好用的了。
作者: yangff    时间: 2011-1-27 00:13
传送门组:
Ruby FFI - Ruby调用C库最棒的工具
http://www.javaeye.com/news/4010 ... y-of-the-best-tools

FFI for Ruby Now Available
http://blog.headius.com/2008/10/ffi-for-ruby-now-available.html

More on Ruby FFI
http://wmeissner.blogspot.com/2008/11/more-on-ruby-ffi.html

作者: 苏小脉    时间: 2011-2-6 13:48
Ruby 1.9 使用了一个效率更高、功能更强大的正则表达式引擎——Oniguruma(鬼車)。相比于 Ruby 1.8 的正则表达式引擎来说,Oniguruma 所能描述的再也不仅仅是正则语言了。这其中的一个重要因素是由于递归表达式的出现——正则语言的一大特点就是没有递归。和大多数现代化的引擎一样,Oniguruma 甚至能用来描述上下文无关语言这样的形式语言。可是,“正则表达式”这个术语仍然被各种语言持续使用了下来。可能人们对正则表达式太熟悉了,熟悉到会觉得其名称改为“形式表达式”很别扭。

本帖主要介绍一个新功能:命名捕获组

对于能熟练使用正则表达式的朋友来说,捕获组并不陌生。未经转义的圆括号中的表达式就是一个捕获组,我们之后可以在表达式中引用这个捕获组。传统的引用方式是通过所谓的后向引用,即 \n 的形式,来引用在表达式中从左到右第 n 个出现的捕获组。

这种方式有很多弊端。首先,用户必须维护 1, 2, ..., n 这些数字。一旦发生两个捕获组换位的情况,就要把所有的它们的后向引用都改掉,这对于较长的表达式来说无疑是力气活。如果我们能给捕获组命名,那么即便它的位置变了,名字却不会变,所以无须修改其引用处。

其次,对于代码阅读者来说(把整个表达式背下来的除外),一个数字只不过是一个密码,它本身并不能让读者直接回想起任何已知事物,读者只能从头开始阅读表达式,并按出现顺序找到对应的捕获组,这属于一个简单的破译过程。如果我们给捕获组命名,那么就可以通过一两个单词来简短地描述该捕获组所接受的语言。比如:将一个捕获组命名为“alphanum”,那么读者大概就能猜到这个捕获组匹配的是“alphanumeric”的值,也就是字母或数字。

Oniguruma 命名捕获组是通过 (?<name>regex) 的语法来完成的。name 是捕获组的名称,regex 是捕获组内部的正则表达式。

  1. #coding: GBK

  2. regex = /(?<hex>[0-9a-fA-F])\+\g<hex>/
  3. str = '我们需要取出4+f这个子串!'
  4. p str[regex] # "4+f"
复制代码
如果我们在命名捕获组时配合正则表达式的“忽略空白符”选项(字面值后添加 `x' 后缀),以及在捕获组子表达式后添加 {0} 表示匹配 0 次,就可以大大地提高表达式的可读性。给一个比较复杂的例子,是我之前写的一个匹配电子邮件地址的雏形:
  1. #coding: GBK

  2. # local@domain

  3. $pattern = %r<

  4. # Define subpatterns.
  5. (?<local>       \g<local_t>|\g<quoted>              ) {0}
  6. (?<local_t>     \g<local_a>+(?:\.\g<local_a>+)*     ) {0}
  7. (?<local_a>     \g<alphanum>|[!\#$\%&'*/=?^_`{|}~-] ) {0}
  8. (?<alphanum>    [a-zA-Z0-9]                         ) {0}
  9. (?<quoted>      ".*"                                ) {0}
  10. (?<domain>      \g<hostname>|\[\g<ip_addr>\]        ) {0}
  11. (?<hostname>    \g<label>(?:\.\g<label>)*           ) {0}
  12. (?<label>       \g<alphanum>+(?:-+\g<alphanum>+)*   ) {0}
  13. (?<ip_addr>     \g<zero_255>(?:\.\g<zero_255>){3}   ) {0}
  14. (?<zero_255>    \d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]   ) {0}

  15. # The root pattern is ...
  16. \g<local>@\g<domain>

  17. >x
复制代码
这里,由于使用的是 %r 来构建正则表达式,所以其内部可以直接写普通的注释。每个子表达式后面跟上 {0},表示这里仅仅是定义并命名一个捕获组,并不参与实际匹配。再加上最后的 `x' 后缀,表达式中的空白都会被忽略,所以可以对表达式进行缩进。这个例子就大量使用了命名捕获组,将匹配不同字符串片段的表达式分划到了不同的子表达式。这在便于维护的同时也提高了可读性,和代码模块化是一个道理。

http://szsu.wordpress.com/2010/11/13/regex_email_addr/
作者: 紫苏    时间: 2011-2-7 06:17
80 楼提到了递归性质的正则表达式。Oniguruma 的递归仍然是通过命名捕获组完成的,这里就做一个演示。

我们有一段很简单的代码字符串:
  1. str = <<STRING_ENDS_HERE
  2. void main() {
  3.     int i;
  4.     if (1) {
  5.         int a;
  6.         if (10) {
  7.             int var;
  8.             if (1) {
  9.                 int i;
  10.             }
  11.         }
  12.         int b;
  13.     }
  14. }
  15. STRING_ENDS_HERE
复制代码
而我们希望通过正则表达式来取出语法正确的 if 语句代码块。
  1. regex = /

  2. (?<if>
  3.     if\s*\(\d*?\)\s*{\s*
  4.         (?:int\s*[a-zA-Z]\w*?;\s*|\g<if>)*
  5.     \s*}\s*
  6. )

  7. /xm
复制代码
这里我们限制了一个语法正确的 if 语句为:
if,跟着可能的空白,跟着 (,跟着一个数字,跟着 ),跟着 {,跟着可能的空白,跟着另一个语法正确的 if 语句,或者跟着一个 int 变量的声明,最后跟着 } 和可能的空白。那么我们:

  1. p str[regex]
复制代码
就能捕获到最外围的 if 语句。当 if 后面少了 ( 或 ),或是少了 { 或 },抑或是 int 变量声明少了 ;,就是一个语法错误,那么就不匹配我们上面所写的 regex。比如,我们故意去掉 int var 后面的分号:

  1. str = <<STRING_ENDS_HERE
  2. void main() {
  3.     int i;
  4.     if (1) {
  5.         int a;
  6.         if (10) {
  7.             int var
  8.             if (1) {
  9.                 int i;
  10.             }
  11.         }
  12.         int b;
  13.     }
  14. }
  15. STRING_ENDS_HERE
复制代码
p str[regex] 则是:
"if (1) {\n                int i;\n            }\n        "

因为外围的 if 内部有语法错误,所以只有最里层的 if 被匹配了。

这只是一个很简陋的语法匹配,但演示了如何使用递归匹配。
作者: 苏小脉    时间: 2011-2-8 05:47
本帖最后由 苏小脉 于 2011-4-2 19:40 编辑

要注意区分传统的捕获组引用和 79、80 楼所提到的引用方式的区别。79、80 楼使用的 \g<name> 语法,是将捕获组当作是一个子程序(subroutine)在用,就好比定义了一个函数,每次去调用它都会进行新的匹配。

传统的引用是这样的:
  1. str = 'a1:13:9f:dd:32:72'
  2. regex = /(\h)\1/
  3. p str[regex] # => "dd"
复制代码
`(\h)' 是一个捕获组,而后面跟着一个后向引用,这不会再次进行 `\h' 的匹配,而是拿捕获组匹配后的结果来进行匹配。所以,这个表达式实际上匹配的是两个相同的十六进制数位,那么在 str 中,只有 `dd' 是匹配的。

由于 1.9 支持命名捕获组,那么想要依然使用以上方式的后向引用自然也需要扩展语法。

  1. str = 'a1:13:9f:dd:32:72'
  2. regex = /(?<hex>\h)\k<hex>/
  3. p str[regex] # => "dd"
复制代码
而如果把 \k 替换为 \g,那就和 79、80 楼一样,重新对“子程序”进行了求值,所以可以匹配任意两个十六进制数位。
作者: DeathKing    时间: 2011-2-9 16:05
本帖最后由 DeathKing 于 2011-2-9 16:09 编辑

这是去年的旧事了(2010-06-18)

Matz 博客透露红宝石2.0新功能
http://chinaonrails.com/topic/view/4111/1.html
http://www.rubyist.net/~matz/20100618.html
  1. def foo(a, b: 42, **keys)
  2.    p [a,b,keys]
  3. end

  4. foo(1)             # => [1,42,{}]
  5. foo(2, b: 5)       # => [2,5,{}]
  6. foo(3, b: 4, c: 6) # => [3,4,{:c=>6}]
复制代码
处理可变参数列表就方便了。
作者: DeathKing    时间: 2011-2-10 01:42
本帖最后由 DeathKing 于 2011-2-10 10:50 编辑

New Ruby 1.9 Features, Tips & Tricks: http://www.igvita.com/2011/02/03 ... atures-tips-tricks/

[line]1[/line]

补一些重要的翻译

* Ruby 1.9.X Oniguruma作为新的正则式引擎。(关于Oniguruma引擎的特性,请看之前紫苏的相关介绍);
* Ruby 1.9.X 允许带有默认值的参数放在前面了。
  1. def f(a=1, b); p [a,b]; end;
  2. p f(2) # => [1, 2]
复制代码
* Ruby 1.9.X 默认使用 Object#inspect 来代替to_s;
  1. puts({a:1, b:[2,3]}.to_s) # => {:a=>1, :b=>[2, 3]}
复制代码
* Ruby 1.9.X 加入了一个 Hash#assoc 方法,返回 [key, hsh[key]]
  1. p({a: 1, b: 2}.assoc(:b)) # => [:b, 2]
复制代码
* Ruby 1.9.X 允许你把可变参数放在参数列表的任何位置;
  1. def a(a,*b,c); p [a,b,c]; end
  2. p a(1,2,3,4) # => [1, [2, 3], 4]
复制代码
* Ruby 1.9.X 能对符号使用正则式了;
  1. p :ruby_symbol.match(/symbol/) # => 5
复制代码
* Ruby 1.9.X 提供了一种新的建立Hash的方法,这种方法更加紧凑;
  1. p({a:1, b:2}) # => {:a=>1, :b=>2}
复制代码
* Ruby 1.9.X 提供了一种建立 stabby proc 的方法(个人认为类似于lambda表达式?);
  1. f = -> a,b { p [a,b] }
  2. p f.call(1,2) # => [1, 2]
复制代码
* Ruby 1.9.X 的 Hash 已经有序了;(可以参见之前的讨论)
  1. p({a:1, b:2}) # => {:a=>1, :b=>2}
复制代码
* Ruby 1.9.X 提供了4种调用Proc的方法;
  1. f =->n {[:hello, n]}
  2. p f[:ruby]       # => [:hello, :ruby]
  3. p f.call(:ruby)  # => [:hello, :ruby]
  4. p f.(:ruby)      # => [:hello, :ruby]
  5. p f === :ruby    # => [:hello, :ruby]
复制代码
* Ruby 1.9.X 不再支持 String#each 方法了,请使用chars,bytes,lines,each_char等(个人猜测是因为each容易引起歧义?);
* Ruby 1.9.X 添加了 Enumerable#sample(n) 方法,随机抽出 n 个元素(个人认为是模拟抽样);
  1. p [1,2,3,4].sample(2)  # => [2, 3]
复制代码
* Ruby 1.9.X 添加了Kernel#define_singleton_method 函数,以后定义单例可以这样做:
  1. c = 'cat'; c.define_singleton_method(:hi) { p 'hi' };
  2. p c.hi  # => "hi"
复制代码
* Ruby 1.9.X 提供了能创建生命周期只在块中的临时变量;
  1. v = 'ruby'; [1,9].map {|val; v| v = val }
  2. p v # => "ruby"
复制代码
* Ruby 1.9.X 允许块使用块作为参数了;
  1. b = -> v, &blk { p [v, blk.call] }
  2. p b.call(:a) { :b } # => [:a, :b]
复制代码
* Ruby 1.9.X 将线程映射到 OS 的线程上;(订正请求:"Ruby 1.9 threads are now mapped (1:1) to OS threads! ex: gdb -p [ruby PID], info threads - yes, there is still a GIL.")
* Ruby 1.9.X 加入了新的垃圾回收方法,GC.count和GC::Profiler;
  1. GC::Profiler.enable; GC.start; puts GC::Profiler.result
复制代码
* Ruby 1.9.X 允许你内观 Ruby 虚拟机 YARV 编译好的字节码;
  1. puts RubyVM::InstructionSequence.compile('a = 1; p 1 + a').disassemble
复制代码

作者: 苏小脉    时间: 2011-2-20 01:06
本帖最后由 苏小脉 于 2011-2-20 01:11 编辑

Ruby 1.9.2-p180 前几天发布了,主要修复是 FileUtils 的一个漏洞。

SVN tag:
http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/tags/v1_9_2_180/

Change log:
http://svn.ruby-lang.org/repos/ruby/tags/v1_9_2_180/ChangeLog

下载:
http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.2-p180.tar.bz2
  SIZE:    8815756 bytes
  MD5:    68510eeb7511c403b91fe5476f250538
  SHA256:
06520c4d4b4512d08000f7dfff11d1fabc1d458c3c289c76a2f1ddb7f5a03f4d

http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.2-p180.tar.gz
  SIZE:   11158935 bytes
  MD5:    0d6953820c9918820dd916e79f4bfde8
  SHA256:
9027a5abaaadc2af85005ed74aeb628ce2326441874bf3d4f1a842663cde04f4

http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.2-p180.zip
  SIZE:   12574217 bytes
  MD5:    cf2726e9faa1b84ba798693e649067ab
  SHA256:
65fa1b6b705f6e99a6ff2ae9e122055235525d5577506e1ee8dd3abdaa639961
作者: 紫苏    时间: 2011-2-21 10:33
Ruby 的块有 do ... end 以及 { ... } 两种语法。国际惯例是:如果块的内容可以在一行写完就使用 { ... },否则使用 do ... end。

  1. 16.times { |i| p i }

  2. 16.times do |i|
  3.   case i
  4.   when 0
  5.     # ...
  6.   when 1
  7.     # ...
  8.   when 2
  9.     # ...
  10.   end
  11. end
复制代码

作者: 苏小脉    时间: 2011-2-24 03:57
如果 Ruby 源文件中包含 __END__ 这个指令,那么在其后的内容都会被当作额外的文本数据,解释器不会对其进行求值。其内容会被初始化到一个 File 对象所表示的流中,并赋给 DATA 这个全局常量。
  1. #coding: GBK

  2. DATA.each { |line| puts line }

  3. __END__
  4. 那是我日夜思念深深爱着的人呐
  5. 到底我该如何表达
  6. 她会接受我吗
  7. 也许永远都不会跟她说出那句话
  8. 注定我要浪迹天涯
  9. 怎么能有牵挂
  10. 梦想总是遥不可及
  11. 是不是应该放弃
  12. 花开花落又是雨季
  13. 春天啊你在哪里
  14. 青春如同奔流的江河
  15. 一去不回来不及道别
  16. 只剩下麻木的我没有了当年的热血
  17. 看那漫天飘零的花朵
  18. 在最美丽的时刻凋谢
  19. 有谁会记得这世界她来过
  20. ……
复制代码
这里是在源文件中嵌入了一段歌词,并通过 Ruby 把歌词打印了出来。

__end__ 指令必须独自占一行,前后不能有别的东西。它的语意是“程序末尾”。
作者: 紫苏    时间: 2011-3-23 10:56
本帖最后由 紫苏 于 2011-3-23 11:05 编辑

将对表达式的求值推迟到真正需要其值之时的行为被称为惰性求值。惰性求值在基于表的函数式语言(如 Haskell、Lisp 及其派生语言)中尤其有用,它使得在不产生死循环的情况下定义无限表成为了可能。
  1. def i
  2.   5
  3. end

  4. def j
  5.   3
  6. end

  7. res = if true then i else j end
复制代码
这是一段 Ruby 代码,最后一行是命令式语言中常见的一种惰性求值。此处,只有 i 和 j 的其中一个最终会被求值,而永远不会出现他们同时被求值的情况。然而,如果把最后一行替换为:
  1. def fn(pred, u, v)
  2.   if pred then u else v end
  3. end

  4. res = fn true, i, j # => 5
复制代码
那么,i 和 j 都会预先被求值,然后才传递给 fn。这被称为热情求值及早求值
在 Ruby 中,和大部分命令式语言一致的 if ... then ... else ... 以及函数等结构都拥有惰性求值的特性,而同样是在语言层被支持的块、闭包、纤程和线程亦是如此。

本主题 37 楼曾介绍过 Ruby 1.9 的纤程,其用到的例子程序中的纤程是一个生成 Fibonacci 序列的工人纤程。实际上,这个工人纤程就相当于基于表的函数式语言中的一个无限 Fibonacci 表,程序只在需要序列中某个 Fibonacci 数的时候才进行求值,否则生成一个无限表将会产生死循环,从而导致 OOM。

线程和纤程红花绿叶是一家,在惰性求值这一点上是没有差别的。

闭包则是一个有趣的实例——

  1. fib = lambda do |n|
  2.   case n
  3.   when 1, 2
  4.     1
  5.   else
  6.     fib.call(n - 1) + fib.call(n - 2)
  7.   end
  8. end

  9. fib.call(6) # => 8
复制代码
在 fib 还正在被定义的时候,竟然也可以引用 fib 来定义它本身。在数学中,这是很常见的递归定义,而惰性求值则使得这一数学技巧在程序语言的世界得到了完美的演绎。当然,同样的结构也可以由函数来完成,但 Ruby 的函数并非一级函数,并不能像 Proc 一样被当作闭包绑定到一个局部变量上。这使得这里的赋值语句尤为显眼,更能体现出惰性求值的特性。倘若这里不是闭包,而是用了如 a = a + 1 这样的语句,且 a 在之前又没有定义,那自然就会发生异常了。

布尔逻辑运算符的短路特性,有时也被称为惰性求值,因为它实际上也是 if ... then ... 结构。逻辑或运算时,如果对左边的运算元求值的结果是真,那么就不会继续求右边的运算元的值了。

与惰性求值相对的热情求值,在 Ruby 中也有很多实例。比如,Ruby 的所有字面值,都是被热情求值的。本主题 65 楼曾提到了正则表达式在词法分析时便已进行求值,那就是一种热情求值,其余字面值也均是在词法分析时就构建了相应的对象。另外一个比较明显的例子是字符串的内插求值:
  1. $bar = :zhangsan
  2. str = "#$bar"
  3. $bar = :lisi
  4. p str # => zhangsan
复制代码
此处在字符串中内插了 $bar 这个变量,然而由于是热情求值,在字符串出现后才改变 $bar 的值,为时已晚。
作者: 苏小脉    时间: 2011-3-26 15:25
  我们曾经多次提到过一个术语——全局解释器锁(Global Interpreter Lock,下简称 GIL)。GIL 是一个通用的术语,而 Ruby 1.9 的开发员似乎又赋予了 Ruby 1.9 的 GIL 另一个特有的名称——Giant VM Lock,硬翻为中文就是“巨型虚拟机锁”。
  在官方和其它一些 C/C++ 的 Ruby 实现(如 Rubinius),以及其它有着相似线程模型的动态类型语言实现(如 CPython)中都存在着 GIL。顾名思义,它是被集成在解释器核心、为解释器量身打造的一个全局的互斥锁,用来保护解释器内部的共享资源。
  虽然 CRuby 解释器的核心部分是线程安全的,但这并不代表本地扩展库提供的代码也是线程安全的(当然,编写扩展的人可以刻意地去写出线程安全的代码)。当我们使用标准库、第三方 Gem 时,我们很多时候都是在使用包含了线程不安全代码的本地扩展,而这些本地扩展多少要访问一些解释器核心的共享资源。一旦线程不安全代码与解释器内核在没有任何线程安全措施的情况下同时访问这些资源,就可能发生各式各样的多任务问题,尤其是竞争条件。这些问题使得我们的程序充斥着奇怪的八阿哥却又极其难以调试,所以我们迫切地需要一个措施来实现线程安全。
  通常保证线程安全性有四种途径:
  然而,前三种途径并不能解决 Ruby 解释器的资源分配问题。解释器的共享资源对于一个解释器实例(解释器进程)来说具有全局性。既然解释器核心和本地扩展都需要变异全局静态的数据,那么即便解释器核心可重入,本地扩展仍然可以在并发时变异这些数据,使得解释器核心代码线程不安全。这也强调了一点:可重入并不意味着线程安全。由于涉及到共享资源,仅仅使用第二种途径的线程局部存储自然也是不够的。原子操作的定性和底层平台具体的(虚拟或物理)机器指令有关,对于一个需要跨平台的解释器实现来说,依赖原子操作而实现并发并不是一个好习惯。最后我们只剩下通过互斥锁设定一个内存屏障,建立临界区这个方案。
  同步多任务也可分为细加锁(Fine-grained locking)和粗加锁(Coarse-grained locking)两种加锁方案。细加锁是一种高密度加锁,它在所有和共享资源有关的结构上分别加锁(这些锁相互独立);粗加锁则是一种低密度加锁,它使用一个被所有结构共享的全局锁。
  假设某组织一幢办公大楼,有若干工作人员和若干工作室,工作人员需要按工作室的顺序进入各个工作室工作,而从当前工作室出来到进入下一个工作室之间的这段时间他们也有一些别的任务需要完成。细加锁,就好比这些工作人员一旦进入其中一个工作室就将其门锁住,直到自己工作结束,再解锁出门。这时可能有若干工作人员为了进入某个工作室而被迫等在门外,但也可能有若干工作人员正在完成不需要在工作室中进行的任务(即前文所说的从当前工作时出来到进入下一个工作室之间需要完成的任务)。在另一方面,粗加锁就好比工作室并没有锁,但整个办公大楼有一道大锁,一旦有一个工作人员进入办公大楼就锁住大楼入口,其它任何人都不得进入。每一个工作人员进入后可以自由工作 15 分钟,之后解锁走出办公大楼,让下一个工作人员进入并工作 15 分钟,如此反复。
  这里的工作人员就相当于线程,而工作室则是有着共享资源的临界区,办公大楼好比是整个进程。显然,较之粗加锁,细加锁有更高的并发性,有更多的工作人员可以在同一时刻工作。然而,细加锁在工作人员每次出入工作室时都多了一道解锁、加锁的程序。试想该组织只有一个工作人员,他独自一人在办公大楼中自是通行无阻,也无人和他争夺工作室,又何须频繁加锁解锁?这在现实世界中是举手之劳,在某些语言实现(比如曾经依赖于引用计数的 CPython 垃圾回收器)中却会带来巨大的开销。因此,细加锁会降低单线程程序的执行效率,提高多线程程序的执行效率,而粗加锁反之。
  想必看官业已猜到,粗加锁的这把锁,就是我们的话题——GIL。Ruby 语言的创始人 Matz 在他的 MRI 实现中使用的是 GIL 进行粗加锁。他之所以做出这个抉择,无非是为了不用考虑本地 Ruby 扩展的线程安全问题,以及提高单线程程序的执行效率。此举是否得当当然也是可商榷的。有些人宁愿在写扩展的时候自行考虑线程安全问题,也不愿为了代码简易而丢失效率。而从长远来看,程序迟早将全面步入并发计算领域,为了单线程执行效率而牺牲多线程执行效率的意义似乎越来越小了。
  Ruby 1.8 内置的 Thread 类使用的是绿色线程,而并非本地线程。Ruby 1.8 的解释器通过模拟本地 OS 的排程机制实现了 Ruby 层线程之间的周期性切换。因此,自始至终每个 Ruby 解释器进程都只有一个真正的用于求值的本地线程(不包含解释器的辅助线程)。哪怕有再多的 CPU 内核,在 Ruby 1.8 中也绝不可能实现真正的并发。更甚的是,当你调用某个外部的耗时函数时(比如进行阻塞 Socket),由于调用线程是唯一的一个负责绿色线程调度的本地线程,整个解释器进程都会因之而阻塞,Ruby 层的绿色线程自然也会尽数僵死。出于这两点考虑,Ruby 1.9 的 Thread 类使用了本地线程。然而,由于 GIL 的存在,两个 Thread 的实例仍然不能真正的并发运行。既然如此,将绿色线程替换为本地线程的意义何在呢?
  首先,绿色线程的调度是在用户空间中管理的,在很多情况下下效率不及由 OS 在内核空间中管理的本地线程;其次,本地线程可以解决解释器进程整体阻塞的问题。Ruby 1.9 默认将部分耗时运算(如 Bignum 的乘法)分配到了一个本地线程中运行,并在进行该运算时暂时释放 GIL,直到运算结束再重新收回 GIL。在 GIL 被释放的这段时间里,其它 Ruby 层的线程就可以运行,那么在多核 CPU 的环境下,即便很短暂,也实现了真正意义上的并发。在写本地 Ruby 扩展的时候,用户可以调用 rb_thread_blocking_region 这个函数来进行可能导致阻塞的操作,其内部其实就是封装了一个释放并收回 GIL 的过程。
  为了科学严谨性,我们最后还要强调一个事实,那就是采用 GIL 只是一个实现细节而并非语言特性。比如,同样是 Ruby 语言,基于 Sun JVM 的 JRuby 就不需要 GIL,因为它在底层依赖于有着自己的独特线程模型的 Java 虚拟机。Sun JVM 并不依赖于 GIL 的保护,相反它依赖于细加锁,这和现代化的 GC、高效的 JIT 乃至于整个虚拟机的复杂度都是密切相关的,这也是为什么 Sun JVM 的性能能得到公众的认可。

作者: 紫苏    时间: 2011-4-2 17:11
本帖最后由 紫苏 于 2011-4-2 17:13 编辑

Ruby 是一个发散性很强的语言——同一个问题,通常有继承自众多程序语言的不同风格的解法,这使得有着各种程序语言背景的程序员在使用 Ruby 的时候都能有似曾相识之感。

Perl 的默认变量就是一个例子。Ruby 内置了不少全局变量,它们的语意等同于 Perl 中的默认变量的概念,比如默认字符串分隔符。今天我们介绍 `$/' 这个被称为输入记录分隔符的默认变量。

很多 Ruby 内置的和输入有关的辅助子程序都使用了 $/ 作为默认的输入分割符,比如 String#each_line 方法。

  1. #coding: GBK

  2. <<-ENDS_HERE
  3. 上.虞.县.
  4. 祝.家.庄.
  5. 玉.水.河.滨
  6. 有.一.个.祝.英.台
  7. ENDS_HERE
  8. .each_line { |c| p c }
复制代码
输出:
"上.虞.县.\n"
"祝.家.庄.\n"
"玉.水.河.滨\n"
"有.一.个.祝.英.台\n"

这段程序把我们人类语言中“台词的每一行”打印了出来,这是由于 $/ 的默认值是 "\n"。如果我们在开头加一段代码改变 $/ 的值:
  1. $/ = '.'
复制代码
那么输出就会变为:
"上."
"虞."
"县."
"\n祝."
"家."
"庄."
"\n玉."
"水."
"河."
"滨\n有."
"一."
"个."
"祝."
"英."
"台\n"

这是由于 each_line 使用了 $/ 这个默认的输入记录分隔符,发现它的值是一个小圆点,所以就将小圆点当作了“每一行”的分隔符。这里的“每一行”的语意是我们定义的。

实际上 each_line 接受一个参数,参数默认值是 $/。我们可以通过传递一个实际参数来覆盖 $/:
  1. each_line("\n") { |c| p c }
复制代码
这样一来,无论 $/ 的值是什么,each_line 都会将 "\n" 当作分隔符。

类似的辅助方法还有很多,比如 IO#gets、IO#readlines 等输入方法,他们几乎都是将第一个参数作为分隔符,并设置其默认值为 $/。

最后,$/ 还有一个同义变量——$-0。
作者: 苏小脉    时间: 2011-4-3 20:21
我曾在这一贴中给出过一个深层数组克隆的方法:
http://rpg.blue/forum.php?mod=viewthread&tid=131787

然而,这个方法并不好。Object#clone 的协议有很多部分,而克隆对象成员只是其中之一,其它部分还包含对象的污染状态的复制等。我们重写了 clone 方法,就忽略了原本 clone 的额外的工作。

我们如果只是想进行深层克隆成员,那可以重写 initialize_copy 这个私有的实例方法,它可以被仅仅当作是克隆协议中负责成员克隆的部分。每当 Object#clone 或 Object#dup 被调用时,它们都会回调 initialize_copy 这个方法进行复制构造。所以,initialize_copy 就相当于 C++ 的复制构造函数。
  1. class Array
  2.   def initialize_copy(anArray)
  3.     for i in 0...anArray.size
  4.       self[i] =
  5.       begin
  6.         anArray[i].clone
  7.       rescue TypeError
  8.         anArray[i]
  9.       end
  10.     end
  11.   end
  12. end

  13. foo = [ 1, [ 1, 2 ], 3, [ 4, 5 ] ]
  14. bar = foo.clone

  15. bar[0] = 2
  16. bar[1][0] = 0xffff

  17. p foo, bar
复制代码
这是 Array 的深层克隆。重写 initialize_copy 前,bar[1] 被变异的同时也变异了 foo[1]。

一个泛用于拷贝所有实例变量的例子:
  1.   def initialize_copy(aKlass)
  2.     aKlass.instance_variables.each do |variable_name|
  3.       instance_variable = instance_variable_get(variable_name)
  4.       begin
  5.         instance_variable = instance_variable.clone
  6.       rescue TypeError
  7.       end
  8.       instance_variable_set(variable_name, instance_variable)
  9.     end
  10.   end
复制代码
这里是利用了 Ruby 强大的元编程能力。
作者: zh99998    时间: 2011-4-8 07:57
Ruby命名参数

在设计一个大型库的时候,由于一个方法传递的参数可能很多,而参数默认值会有顺序的限制,如果我们要传递比较靠后的一个参数的值,那么就要把前面的一起传递进去,不太方便,这时我们希望能直接指明要传递哪个参数

Ruby没有原生的命名参数,但是可以使用Hash传递时的语法糖来达到类似命名参数的效果

例如我们想创建一个Window类(与RM的不完全相同,只是举例),在初始化时可以提交很多值,其中x,y,w,h是必填,然后还有些z, windowskin等属性
  1. class Window
  2.   def initialize(x, y, width, height, params)
  3.     self.x = x
  4.     self.y = y
  5.     self.width = width
  6.     self.height = height
  7.     self.z = params[:z] if params[:z]
  8.     self.windowskin = params[:windowskin] if params[:windowskin]
  9.   end
  10. end
  11. @window = Window.new(0, 0, 100, 100, :windowskin => "window")
复制代码

作者: 苏小脉    时间: 2011-4-15 04:29
http://ideone.com/

这是一个在线试玩 Ruby 的 Web 接口,省去了编译一份本地 Ruby 拷贝的工作。它使用的 Ruby 解释器版本是 1.9.2。
作者: 紫苏    时间: 2011-4-19 01:07
本帖最后由 紫苏 于 2011-4-19 01:16 编辑

`Module#undef_method' 是一个值得注意的方法。它的作用是将一个方法从方法分派的任务中移除,使得调用该方法者收到一个 `NoMethodError' 异常(即 `obj.respond_to? :method => true')。然而,`undef_method' 并非是把方法彻底从类结构中移除了,它仅仅是将以符号表示的方法名所标识的方法标记为“未定义”,使得解释器在通过方法分派、反射等机制寻找这个方法时会立即抛出异常。这就逻辑蕴含了另一个事实:哪怕基类或是 mixed-in 模块有同名的方法,在派生类的同名方法被 `undefine_method' 处理后,也不会尝试进行向上搜寻的方法分派。

`Module#undef_method' 还有一个家族成员——`Module#remove_method'。它彻底从当前模块(即 `remove_method' 的接收者。一个 `Module' 的实例)结构中移除一个方法的引用。如此一来,方法分派函数在派生类中找不到一个方法,就会尝试在基类中寻找。
  1. class Base
  2.   def bar
  3.     p :'base bar'
  4.   end
  5. end

  6. class Derivation < Base
  7.   def bar
  8.     p :'derivation bar'
  9.   end
  10. end

  11. d = Derivation.new

  12. class Derivation
  13.   remove_method :bar
  14. end

  15. d.bar                   # => :"base bar"

  16. class Derivation
  17.   undef_method :bar
  18. end

  19. d.bar                   # => NoMethodError
复制代码
可见 `remove_method' 后仍然能找到基类的 bar 方法,但 `undef_method' 后却不能。

再演示另外一个和 eigenclass (Ruby 单例类)有关的例子:

  1. obj = Object.new

  2. def obj.foo
  3.   p self
  4. end

  5. p obj.respond_to? :foo  # => true

  6. class << obj
  7.   undef_method :foo
  8. end

  9. p obj.respond_to? :foo  # => false

  10. Marshal.dump(obj)       # => error
复制代码
可以发现,仅仅是 `undef_method' 并不会移除 eigenclass 中的 eigenmethod(单例方法),所以它不能被 `Marshal' 模块 dump。当我们把 `undef_method' 替换为 `remove_method' 后,就可以 dump 了。

在方法被标记为未定义或彻底移除之前通过反射获取的该方法的 `Method' 对象,在之后仍然能正常运作:
  1. class Klass
  2.   def foo
  3.     p :'Klass#foo'
  4.   end
  5. end

  6. obj = Klass.new

  7. def obj.bar
  8.   p :'obj.bar'
  9. end

  10. m_foo_unbound = Klass.instance_method :foo
  11. m_foo = obj.method :foo
  12. m_bar = obj.method :bar

  13. class Klass
  14.   remove_method :foo
  15. end

  16. class << obj
  17.   remove_method :bar
  18. end

  19. p obj.respond_to? :foo        # => false
  20. p obj.respond_to? :bar        # => false

  21. m_foo_unbound.bind(obj).call  # => :"Klass#foo"
  22. m_foo.call                    # => :"Klass#foo"
  23. m_bar.call                    # => :"obj.bar"
复制代码
这同时也演示了方法的绑定和方法分派相互独立的特性。

最后,Ruby 还提供了一个语法糖 `undef' 以更简洁的语法(后面可以跟上一个纯粹的标识符,而无须符号或是其它字面值,类似与 `alias' 这个语法糖)调用了和 Module#undef_method 相同的实现。`undef' 是 Ruby 的关键字。
作者: DeathKing    时间: 2011-4-22 00:10
元编程相关技术:
元编程:http://ruby-metaprogramming.rubylearning.com/
Metaclass详解:http://www.letrails.cn/archives/ruby-metaclass-tutorials/
作者: 苏小脉    时间: 2011-5-26 09:56
很久没来关注这个主题,分享一些关于 Rubinius 的新闻。上月,EngineYard 的应用云端引入了 Rubinius。不少人还没看清楚这颗早晨八九点钟的太阳,Rubinius 又在上周的 RailsConf 2011 上以通过用户数以万计的测试套件证明了自己的稳定性和正确性——小伙彻底红了。

如果你使用 Ruby,但又对你所使用的 Ruby 实现不够满意,那么你绝对应该关注 Rubinius。Rubinius 的创造者 Evan Phoenix 曾指出,MRI 的架构在 1.8 的时候便已然定性,想要在性能上做出革命性的改变将会导致大部分实现代码的重写。想来也是,毕竟 Matz 当初在制造 MRI 原型的时候只不过是想要一个玩具。Rubinius 从设计的出发点开始就和 MRI 不同——它的目的就是为了带给用户高效的 Ruby 代码。它的设计和实现都是建立在近几年来对进程虚拟机的大量理论研究和实践的变革之上的。Rubinius 有自己的字节码编译器,基于 Smalltalk-80 蓝图的虚拟机,当代的分代垃圾回收器(generational GC),以及由性能分析(profiling)驱动的即时编译器(JIT)。相形之下,还在使用上个世纪的保守的标记-清除 GC,同时又缺少对 JIT 的支持的 YARV 顿显失色。更令人振奋的是,Rubinius 已经开始在它的 Hydra 分支上进行全局解释器锁的移除工作。据开发员们透露,在 GIL 被移除后,最多只需要部署几十把细锁。这意味着在 Rubinius 虚拟机上运行的 Ruby 线程,将获得空前的并发能力。

贫一句:世界是 YARV 的,也是 JRuby 的,但归根结底还是 Rubinius 的! =)

关注 Rubinius:
http://rubini.us
http://twitter.com/#!/rubinius
作者: zh99998    时间: 2011-6-14 16:56
标题: Ruyb 之 Block, Proc, Lambda
Block 不是对象,是Ruby的语言特性,近似于闭包(Closure)。
范例:
def meth
  res=  yield
  "Block called returns #{res}"
end
puts  meth do next “next_value” end    #Block called returns next_value
puts  meth do break “break_value” end   # break_vcowcuo错误哦alue
def my
  meth do return “reutnr_value” end
end
puts my ()      # return_value

红色部分就是 Block.

block 之 next
block中的 next [value] 导致 block结束,并返回[value]给调用函数。


block 之 break
block中的 break [value] 导致 block结束,并导致调用block的函数也返回,返回值是[value]。


block 之 return
block中的 return [value] 导致 block结束,并导致定义block的函数返回,返回值是[value]。



ProcProc 是Ruby 对 block的面向对象的封装。
范例:
def meth (&proc)
  res=  proc.call
  "Proc called returns #{res}"
end
puts  meth
do next “next_value” end    #Proc called returns next_value
puts eth do break “break_value” end   # 出错
def my
  meth do return “reutnr_value” end
end
puts my ()      # return_value

红色部分就是 Proc.

Proc 之 next
Proc中的 next [value] 导致 Proc结束,并返回[value]给调用函数。

block 之 break
Proc中不能使用break,这回导致异常。


block 之 return
Proc中的 return [value] 导致 Proc结束,并导致定义Proc的函数返回,返回值是[value]。


LambdaLambda 是Ruby 对 Proc进一步的封装。
范例:
def meth (&proc)
  res=  proc.call
  "Lambda called returns #{res}"
end
def my
  meth (&lambda do return “reutnr_value” end)
end
puts my ()      # Lambda called returns return_value

红色部分就是 Lambda.
Lambda 和 Proc的区别就在 Lambda中的 return 导致 lambda 函数结束,并返回调用lamdad的函数(Proc和Block都是从定义的它们的函数放回)


Block,Proc,Lambda的区别总结
转载自http://www.cnblogs.com/napoleon_liu/archive/2009/11/25/1610638.html
作者: 苏小脉    时间: 2011-7-17 09:25
不要盲目地认为当前的 `self' 同时也决定了常量的上下文。Ruby 有三个隐式上下文:`self'、`klass' 以及 `cref'。本主题早先有两楼讲述了如何理解 `self' 和 `klass' [1],用一句话总结就是:`self' 决定了调用未指定接收者的方法时的默认接收者以及如何解析当前作用域下的量,而 `klass' 决定了当前定义的方法属于哪个类或模块。而最后一个 `cref',则是决定了当前的量作用域,也就是在引用常量时决定在哪个类或模块查找,定义常量时决定它最终归属于哪个类或模块。再换句话说,就是决定了当前的常量命名空间。

先来看一个例子:

  1. X = Class.new {}
复制代码
这段代码中,Class.new 的块开启了一个新的作用域作为正在分配的 Class 对象的主体。很多人在这里就想当然地认为这个块相当于 class X ... end,在块中所写的代码会和 class X ... end 之间的代码有相同的效果,而这是错误的。在这个块中,只有 `self' 和 `klass' 这两个隐式上下文被设为了正在分配的这个 Class 对象本身,但 `cref' 没有变。
  1. X = Class.new do
  2.   p self
  3.   def foo
  4.     p :foo
  5.   end
  6. end

  7. x = X.new
  8. x.foo
复制代码
这段代码输出了 Class 对象本身和 `:foo',就是因为 `self' 和 `klass' 都指向了这个 Class 对象本身。然而,当你这样做的时候:
  1. X = Class.new { Y = 1 }

  2. p X::Y
复制代码
解释器抛出一个警告:
1.rb:5: warning: toplevel constant Y referenced by X::Y

这个警告是提醒用户,Y 是顶层常量,不需要用 X 来修饰它 [2],直接引用 Y 就可以了。

很明显,这是因为当前的常量上下文没有因为程序执行进入了 Class.new 的块而改变,所以在块内部定义常量的时候,`cref' 仍然是顶层。Object#instance_eval 和 Module#class_eval 影响的也只有 `self’ 和 `klass',但不会改变 `cref'。那么什么才会使得 `cref' 改变?只有语法上的 class ... end 和 module ... end 定义。
  1. class X
  2.   Y = 1
  3. end

  4. p X::Y         # => 1
复制代码
那么如何不在不建立 class ... end、module ... end 结构的情况下显式指定常量归属?直接通过 `::' 运算符修饰在某些场合下好使,但这在像 X = Class.new { ... } 这样的场合下是不会成功的,因为在块执行的时候 X 还没有定义 [3]。同时,这种方法是对 `X' 这个符号做了硬性编码,但有时我们可能会需要让这个命名空间本身是不定的(变量),比如在 Class.new 的块中定义属于正在分配的这个 Class 对象的常量。这时,有一对很好用的方法就是你的朋友:Module#const_get 和 Module#const_set。
  1. X = Class.new { const_set :Y, 1 }

  2. p X::Y           # => 1
  3. p X.const_get :Y # => 1
复制代码
由于 const_get 和 const_set 的接受者就是它们操作常量时所使用的命名空间,动态性就实现了。

[1] 76、77 楼讲述了 `self' 和 `klass'(default definee)。
[2] 用 X 修饰常量则表示常量定义在 X 这个命名空间中。
[3] Class.new { ... } 连块一起,整个返回后才会把返回值赋给 X,在此之前 X 都会保持未定义状态。
作者: 六脉神剑    时间: 2011-7-20 08:14
Ruby 1.9.2-p290 发布了:

http://www.ruby-lang.org/en/news ... 2-p290-is-released/

这次的发布又是一堆 BUG 修复。
作者: DeathKing    时间: 2011-8-4 09:08
Ruby 1.9.3 第一个预览版已经发布了,这是一个参照级的版本,有可能包含Bug,但是这些Bug会在下一个版本Ruby 1.9.3-p0中修正。该版本自1.9.2以来的改进包括:

1. 许可证更改:

更改了Ruby 的许可证,从GPLv2双许可证更改为2-clause BSDL双许可证。

2. 升级了C API:

rb_scan_args() is enhanced with support for option hash argument extraction.
ruby_vm_at_exit() added.  This enables extension libs to hook a VM termination.

3. 更新了库:

包括ARGF、 Array、Bignum、Encoding、File、IO、Kernel、Module、Random、String、Time、 Process等。

4. 语言变化:

正则表达式现在支持 Unicode6.0(新的字符和脚本)
正则表达式现在支持Age属性(实验 阶段)
使用指令开启/关闭缩进警告。

发行日志:http://svn.ruby-lang.org/repos/ruby/tags/v1_9_3_preview1/NEWS
详细更新信息:http://svn.ruby-lang.org/repos/r ... _preview1/ChangeLog
下载地址:http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-preview1.zip




欢迎光临 Project1 (https://rpg.blue/) Powered by Discuz! X3.1