设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
楼主: DeathKing
打印 上一主题 下一主题

[讨论] Ruby/RGSS Tips 每日一更 [技术区的版聊帖?]

  [复制链接]

Lv2.观梦者 (管理员)

八云紫的式神

梦石
0
星屑
539
在线时间
1238 小时
注册时间
2008-1-1
帖子
4282

烫烫烫

74
发表于 2011-1-19 08:22:33 | 只看该作者

单例类

以下引用紫苏在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用数组即可
rm for linux(wine)制作中,期待夏娜SAMA能实现到webrm上
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
73
发表于 2011-1-18 10:02:31 | 只看该作者
本帖最后由 紫苏 于 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
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
110
在线时间
953 小时
注册时间
2007-4-25
帖子
805
72
发表于 2011-1-16 04:09:30 | 只看该作者
你可能听说过“解释性语言”这个词,它意味着一种间接被软件解释器程序执行(解释)的语言;与之相对的是“编译性语言”,即可以直接被翻译为机器码并交给 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 了。

点评

研究 RMVX 所发展出来的新语言开发的志向,可以一试撒..  发表于 2011-1-16 14:06
[email protected]:~> repeat 1 fortune
Matz is nice, so we are nice.
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
71
发表于 2011-1-15 13:15:22 | 只看该作者
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 }。

点评

Object#=== 应是直接返回 == 的结果;Class#=== 上面说了,判断 self == obj.class;<=> 是承自 Perl 的比较运算符,左边大、相等、右边大分别返回 -1,0,1  发表于 2011-1-18 08:28
判断的是值还是什么的说? <=> 这个呢??  发表于 2011-1-16 16:44
飞船运算符是 `<=>' 嘛 >_<  发表于 2011-1-16 10:34
【飞船运算符】  发表于 2011-1-16 08:58
专门用于 case 时的相等性判断 OvO  发表于 2011-1-16 02:45

评分

参与人数 1星屑 +132 收起 理由
zh99998 + 132 辛苦了o.o

查看全部评分

回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
110
在线时间
953 小时
注册时间
2007-4-25
帖子
805
70
发表于 2011-1-9 02:37:57 | 只看该作者
本帖最后由 苏小脉 于 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"
[email protected]:~> repeat 1 fortune
Matz is nice, so we are nice.
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
69
发表于 2011-1-8 14:56:55 | 只看该作者
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)

点评

所以很早以前还幻想实现++a或者a++之类的事情。  发表于 2011-1-8 15:39

评分

参与人数 1星屑 +132 收起 理由
zh99998 + 132 那啥,突出贡献奖,乃已经可以申请产地了,.

查看全部评分

回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
110
在线时间
953 小时
注册时间
2007-4-25
帖子
805
68
发表于 2010-12-27 00:08:22 | 只看该作者
Ruby 1.8 的 Kernel#require 可以接受一个绝对路径,如果不是绝对路径,就会在 $: 保存的搜索路径中进行搜索,但这默认不包括当前工作路径。

Ruby 1.9 多了一个 Kernel#require_relative,接受一个相对于当前源文件的路径,这样就不用显示地去把当前工作路径添加到 $: 中或显示地指定 `.' 了。要注意的是在 irb 中这个函数是无法使用的,因为 irb 并没有一个明确的源文件定义。

点评

老板真勤快……双蛋活动不参加么?  发表于 2010-12-27 00:13
[email protected]:~> repeat 1 fortune
Matz is nice, so we are nice.
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
67
发表于 2010-12-26 00:14:10 | 只看该作者
想在第一时间获得 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 源代码什么的。
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
110
在线时间
953 小时
注册时间
2007-4-25
帖子
805
66
发表于 2010-12-25 23:56:38 | 只看该作者
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

点评

可以试试,我对 Rails 没认知……  发表于 2010-12-26 09:58
p136能支持Rails3么?  发表于 2010-12-26 09:17
[email protected]:~> repeat 1 fortune
Matz is nice, so we are nice.
回复 支持 反对

使用道具 举报

Lv1.梦旅人

梦石
0
星屑
61
在线时间
24 小时
注册时间
2008-8-5
帖子
1924
65
发表于 2010-12-24 03:57:05 | 只看该作者
正则表达式字面值的对象构建

  之前曾谈到了字符串扣留的概念,那么对于 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/
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-4-20 09:29

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表