实现为 operator 是不是意味着它又是一个 method 的语法糖?我们可以覆盖它?YAVM 虚拟机上有对应的原语级操作么?
#2 楼 @juanito 赞同 NullObject。但是 NullObject 只是转移了 overhead,并没有消除它。当我们有大量的 NullObject 的时候,又如何方便地管理呢?是用 https://github.com/avdi/naught 这种 builder 么?
友情 UP。
想要书的……不信一件 T 恤还不值一本书的钱……
#2 楼 @rubyu2 个人持不同意见。我是支持左边的写法,论点如下:
sum(x : Int32)
错了,我们只调试这个方法即可,不影响其它方法。之前还在说 Ruby 怎么为 foo(*bar)
定类型呢(详见:https://ruby-china.org/topics/25428#reply4),结果 Crystal 说我们用多态吧,2333。(BTW,Crystal 能不能把模式匹配搞进去啊?)
其实我想听听 @luikore 的深入见解。
Ruby under a Microscope 是本好书,之前 Ruby Hacking Guide 中译版夭折,就一直期待这本书能够出来。出版后一定支持一本!
这个问题值得好好讨论一番。下面都是一家之言,如有纰漏,欢迎交流指正!
最初的起源可见 hooopo 兄十个月前发的帖子:https://bugs.ruby-lang.org/issues/9999。这个问题甚至可以从 Feature #9999 追溯到 Feature #5583。
继 Soft Typing 以后,Jeremy Siek 等人于 2006 年出来的 Gradual Typing(科普文章可见Jeremy Siel - 什么是 GRADUAL TYPING)早就在动态语言阵营中挂起一阵混搭旋风。也有很多人蠢蠢欲动往 Ruby 以及 Rails 里面加类型系统,比如:
是否引入类型系统,怎么引入,以及要用类型系统,都得看动机。
为了规避不必要的运行时类型检查,以提高程序的运行效率,那么引入静态类型检查,在语法分析阶段确定好运算符/方法的分派是非常有必要的,但 Ruby 骨子里的动态性让这个有些难做,或者说,只能让程序做部分的静态类型检查(基本上就是 Gradual Typing 的思想)。正如视频里面提到的那样,Go 语言的 Structural Subtyping 跟 Duck Typing 有异曲同工之妙,都是面向协议编程(或者说面向接口编程)。
反之,如果引入类型系统不当,反而会带来很多麻烦。Ruby 里面没有多态,有时候实现多态要靠参数列表来实现,请考虑下面的方法定义:
def foo(*bar)
# blah blah
end
怎么给 bar
加 annotation?Array
?Array<T>
?由于数组内容通常还可能是不同类的实例,合法的类型标注可能是 Array<Object>
,这不等于啥都没说嘛!要不要用 Dependent Type、**hash
和 &blk
又怎么处理、为了让类型系统能够元编程,是否也要让类型成为 First class citizen?这些都是问题。
部分人讨厌类型系统,是因为他们觉得写类型注释很恼人,很啰嗦。类型注释绝不是啰嗦,它其实也是一种 Specification,表明了程序员的意图。一方面来说,类型注释描述的是做什么(What to do),而具体的代码才是怎么去做(How to do)。我们有 Curry-Howard 同构告诉我们程序即证明,那么类型注释就是这个 Goal,我们编写代码让类型系统接受,就是去满足我们的 Specification,两者应该是相辅相成的。程序员觉得类型系统麻烦,只是因为在面向一些复杂类型时无从下手而已(比如一个很经典的问题就是:简单类型的 Lambda 演算系统中,没法给 Y 组合子定类型)。
类型系统还有一个更重要的方面:类型安全。虽然 Ruby 本身是动态类型的语言,但它是强类型语言,所以目前 Ruby 在类型安全这方面做得不错了。前段时间有个叫 Rubype 的 Gem,作者声称是受Gradual Type Checking for Ruby这篇文章启发,而后面那篇文章也明确 Ref 了之前提到的 The Ruby Type Checker 这篇 Paper。
这个 Gem 是在用户自定义级方法的层次上完成类型检查,再讨论 Rubype 之前,请考虑下面的定义:
class Vector
attr_reader :x, :y
def initialize(x, y)
@x = x
@y = y
end
# 这里将向量的数乘(Scale)和点乘(Punktprodukt)都定义成了一个多态的 *
def *(v)
case v
when Numeric
Vector.new(x * v, y * v)
when Vector
x * v.x + y * v.y
else
raise TypeError
end
end
def to_s
"Vector: <#@x, #@y>"
end
end
由于 Ruby 是不会做参数类型检查的,所以我们要自己去检查参数类型,并根据类型来分派需要执行的操作。显然将一个向量与字符串相乘是没有意义的,因此对于其它类型来说,我们要抛出 TypeError
。实际上很多 Ruby 程序都有这样的需求,就跟函数的定义域一样,一个方法可能只是针对某个或某几个特殊的域(Domains)来定义的,对于不在这个定义域的参数,我们必须抛出一个错误,但每次都这么做,很容易违背 DRY 的原则。想一想也是,本来函数定义域这个东西,是属于“声明范畴”的,却要放在命令范畴让程序员自己写代码来做检查,确实有点离谱,所以就有了 Rubype。
可惜的是,Rubype 它只能算作是一种语法糖,只是像 attr_*
系列方法那样,通过声明式的方式,将函数的类型注释维护起来。与手动写代码不同,Rubype 不但检查参数类型,还检查返回值类型,这也使得它的 Runtime Overhead 非常严重。Rubype 没有从根本上解决问题,当然要想从根本上解决这个问题必须在解释器上开刀。不过 Rubype 的“Meaningful error”和“Executable documentation”还是比较有亮点的。
扯了这么半天,观点还是比较零碎,总结一下:
类型系统往深了扯还涉及到程序设计语言的形式语义、程序正确性等问题,最近看屈延文院士的《形式语义学》一书中有这么一个说法:
那么是否说采用 Dijkstra 方法就能保证程序正确呢?假如是一个理想的程序设计者,从不会出现任何错误,那么该方法设计出的程序是可靠的。我想,这就如同一个数学定理的证明一样,会有人证错。大概 Dijkstra 本人,如领导许多程序员按照他的方法设计程序,当程序员设计完程序后,向他说程序已经没有错误,他大概也只能相信他们是对的,而不能保证他们是对的。
突然感觉就编程这方面,我们还任重而道远啊…………
最后,Tom 那句话非常在理,这玩意儿不是脑袋一热一个 Pull Request 就可以搞定的,可能还真得一个需要 PhD Thesis(搞不好还得好多个 )。
我记得 Ruby 的文档都是会给使用范例的,类型什么的文档都说得很清楚。
我感觉 Rubype 并不是真正意义上的 Gradual Type Checking,它只能算是一个 framework,本质上也是一个动态类型检查,只不过它把本来应该由程序员编写的运行时类型检查,通过声明式(Declarative)的方式让解释器执行罢了。
我认为引入类型系统,一方面是为了确保程序的正确性(强类型),另一方面是加速程序的运行(尽早确定操作符,而不是动态派发)。但从 Benchmark 的结果来看,如果不再解释器的层次上实现 Type Annotation 和 Type Checking,实际应用的前途不太大啊。
私货:http://deathking.github.io/2015/04/10/what-is-gradual-typing/
感觉这种算法题还是不要用 Ruby 吧,确实那个效率堪忧。 Ruby 拿来做做 Project Euler 还是挺不错的。
呃,跟楼主竟然是校友……
招实习生么?1 月中旬到农历新年前的样子。
囧死……前几天刚买。
自顶一下
哎呀,说过我经历过最常见的吧!莫过于词法作用域了!
给没有逻辑学基础或者其他相关知识的同学,老是以为外面那个foo
和bar
里面那个foo
是同一个foo
,我倒是觉得怎么看怎么都是两个不同的东西。
foo = 1
def bar
foo = 2
end
bar #=> 2
foo #=> 1
#6 楼 @jiwoorico 怪我大意,若是回帖时声明此乃 2.0 之 Feature,想必那是极好的。
Why not? We also have splat operator. http://stackoverflow.com/questions/22026694/ruby-keyword-arugments-can-you-treat-all-of-the-keyword-arguments-as-a-hash
def forward_all(*arguments, **keyword_arguments, &block)
SomeOtherObject.some_other_method *arguments,
**keyword_arguments,
&block
end
gnuplot?用贝塞尔曲线去平滑吧,我记得 gnuplot 自带这个选项。不过用贝塞尔曲线去平滑的话,会在部分波动较大的点失真。
是我的错觉么?还是真把 Rails Developer 当作 Full Stack 了?怎么最近看到的招聘都是恨不得招一个全部活都给揽完的人啊!
虽然我没啥工作经验,也不知道贵司的具体分工,但是还真是想吐槽几句,看您这岗位职责,符合这些要求的人为啥不去创业呢?
#3 楼 @flowerwrong 看了你的代码,豁然开朗。确实有效,感激不尽!
编译器/解释器与程序员之间的博弈,看把脏活给谁做。
简单说下 Ruby 吧。由于 Ruby 是动态语言,所以类型检查是放在运行时的,不像 C 等静态类型语言,在编译时就可以确定类型。
不要给我说 Ruby 是鸭子类型,那是 Ruby 层面,回到 CRuby 的实现层面,解释器需要对数据进行区分。CRuby 中的对象就是一个VALUE
,根据 VALUE 是否为为特殊值Qtrue
、Qfalse
等、VALUE
是否为奇数判断是否为FIXNUM
,然后再解释为RXXX
的结构体。对于 C 来说,编译时做完类型检查就一劳永逸了,而 Ruby 却不得不在每次求值时做一次检查(为了知道这个VALUE
是什么)。[之前貌似看到了说 Ruby 要引入静态类型?发帖前搜了下社区,好像是 Feature #9999,参见 Further Reading No.3]
再比如,C 语言里面的字符串是非常 Dirty 的,比较轻量的就是栈上的字符数组(临时变量),再大不了就是堆上的存储空间(malloc
等分配),为了存储一个大小为 X 字节的字符串,至多需要 X+1 字节的空间(算上\0
),编译器不带检查溢出,因为它认为那是“程序员的事儿”,所以编译器就非常省事儿了。Ruby 就不一样了。Ruby 的字符串在建立时就需要经过一些检查,而且为了高效利用内存,Ruby 按照自己的方法设计了数据结构(比如下面)。很明显,Ruby 中一个大小为 X 字节的字符串,需要远多于 X 字节的空间,不要忘了还有一个指向这个RString
结构体的VALUE
,占用了一个机器字!
struct RString {
struct RBasic basic;
long len;
char *ptr;
union {
long capa;
VALUE shared;
} aux;
};
总而言之,你爽了,编译器/解释器就苦逼了。Dennis 把脏活推给程序员,Matz 让 Ruby 任劳任怨,各有各的道理。
B.T.W. 如果想深入了解 Ruby 语言背后的细节,《Ruby Hacking Guide》是一份非常棒的参考资料。或者学习《The Structure and Interpretation of Computer Program》的第四章和第五章,了解一下如何做一个求值器,你应该能对这个问题有深入体会的。
不知大概几时结束。家在成都的郊区县,太晚了还不方便回去。
#6 楼 @pynix 既然都做了这么多工作了,可以继续分析下去,做做 Diagram,理一下整个逻辑(解释流程)可能会好一点。我目前看过最漂亮的 C 语言实现的解释器应该是 Lua,云风大神也写过 Lua 的源码分析。
还有,RHG(Ruby Hacking Guide)是一本很好的书,分析地也比较透彻。作者不是简单地向读者介绍如何去分析源码,而是在理解源码的接触上,又自己整理了一下。照这个势头,你完全可以写成 mruby hacking guide。
mruby 的词法、语法分析可以略过,记忆中 Ruby 是拿 yacc 整的,其实也挺麻烦的。我觉得详细分析一下 Ruby 中的数据类型在 C 语言层面上是如何表示的。之前在 RHG 上看到 RString 和 RArray 里面有个 aux 域,感觉有点精妙。
读源码是件乐事,可以从中学到很多东西,之前为了在实现一个 Scheme 解释器的时候,我就参考了 Lua 和 Ruby 的实现代码(Python 方面则是参考一个师兄之前做的讲座有介绍 CPython 的实现),偷学了不少技巧,就比如 Ruby 中对 VALUE 的处理。受用无穷。
楼主加油!
大有发展成 mruby hacking guide 之势。^_^
说白了就是 Lambda 演算和 Church 编码。
@chenge 我拿 Scheme 写的,但基本上还是命令式的数据结构与算法的写法https://github.com/DeathKing/Hit-DataStructure-On-Scheme 。
我觉得完全没必要用 Ruby 写,而且如果没有对数据结构与算法本身还有 Ruby 本身有深厚了解,写出来的代码也不够 Rubyist。另外,用 Ruby 和用 C 没有太大的 本质上的不同,包括我上面用 Scheme 写的代码也是(打着“函数式”语言的旗号,但很多地方还是用 set! 来改变状态)。完成了这些代码后,我觉得特别是用 Scheme 来写命令式的数据结构有时候麻烦地要死。
有本 Pure Functional Data Structure,门槛有点高,不太适合(低年级)本科生。