本文属于“深夜睡不着,那就开个脑洞吧”系列。
从我开始接触编程开始,编程语言的控制结构if...else...
便是所有语言的标配。从我最开始学的 VB,到后来的 C,Java,Lua,JavaScript 皆是如此。而当我开始学 Ruby 时,我发现 Ruby 除了if
,竟然还有一个在当时的我看来是代替if not
的unless
,这真是非常神奇了。再到后来接触到 Python 和 Perl,Perl 是第二个我所知的有unless
结构的语言。
虽然我一开始也有点惊讶unless
的存在,但是写习惯了之后,觉得这货在很多时候的表现也挺好的,在某些时候能让代码的可读性更强,更像自然语言;另外,大家在也总结出了一些 unless 的风格指南:
在单行语句的时候喜爱使用 if/unless 修饰符。另一个好的选择就是使 and/or 来做流程控制。
# bad
if some_condition
do_something
end
# good
do_something if some_condition
# another good option
some_condition and do_something
以及:
永远不要使用 unless 和 else 组合。将它们改写成肯定条件。
# bad
unless success?
puts 'failure'
else
puts 'success'
end
# good
if success?
puts 'success'
else
puts 'failure'
end
每天都能在 Ruby 代码看见unless
,本来应该对他习以为常,但我的脑洞就一直很大啊,有时候就会问,为啥要有unless
呢?
根据 wiki 介绍,Ruby 的设计受到了 Perl, Smalltalk, Eiffel, Ada, and Lisp 的影响:
According to the creator, Ruby was influenced by Perl, Smalltalk, Eiffel, Ada, and Lisp.
在这几门语言中,Perl 上面已经提到了,是有unless
控制结构的。此外,我最近搜索了一下,发现 Common Lisp 竟然也是有unless
表达式的。以下 Common List 代码是从百度贴吧复制过来的,写的一个神经网络,这是摘抄的一部分(贴吧真是代有人才出):
(defun weights-change-network-module()
(unless (= (network-weight-change-rate-fn) 0)
(Batch-adding-neurons *new-neurons-number* *new-nodex-number* *new-nodey-number* )
(repeat-matching-nodes ));节点重新分配
(dolist (obj *neurons-list* )
(unless (= (weights-comprehensive-regulation obj ) 0)
(modify-weights-module (neurons-name obj)(weights-comprehensive-regulation obj ))))
把这段代码翻译成 Ruby 风格,大概就是这样:
def weights_change_network_module()
unless network_weight_change_rate_fn == 0
Batch_adding_neurons *new_neurons_number* *new_nodex_number* *new_nodey_number*
repeat_matching_nodes
end #节点重新分配
dolist (obj *neurons_list*)
unless weights_comprehensive_regulation(obj) == 0
modify_weights_module (neurons_name obj)(weights_comprehensive_regulation obj )
end
end
可把自己牛逼坏了,居然看得懂 Lisp,我得叉会腰 好吧,其实并没有,我完全不懂那些 *new_neurons_number*
是什么东西
所以,我第一个感觉应该是 Matz 应该是受到 Lisp 或者 Perl 的启发,在设计 Ruby 时,加入了unless
。
我一直觉得 if condition; do something; else; do something else; end
是一个非常符合人类思维的表达,直到 17 年 8 月,一个同学给我安利了一个游戏叫 HRM,界面如下:
这其实是一个编程小游戏:右边是程序代码,在这一关能使用的命令非常少,就只有“输入”,“输出”,“加法”, “减法”和三个 jump,左边的方块 7, 2, -3, -8...是代表给程序输入,中间地上的格子,0,1,2 是三个存储器,相当于变量。这道题的目标是每次取两个输入值,然后输出二者中比较大的那个,以此循环。
这时候,我才忽然意识到,我们所写的:
if a - b > 0
p a
else
p b
end
在电脑看来,应该是这副模样:
00 if a -b > 0
01 goto 04
02 print b
03 goto 05
04 print a
05 end
去掉 goto 语句,就会有一种这样的感觉,和我们直接感觉有些颠倒:
if a - b > 0
print b
print a
那么,Ruby 真的时这个游戏里的这样去实现的吗?我也不知道,所以测试了下:
#!/usr/bin/env ruby
code = <<CODE
if a - b > 0
p a
else
p b
end
CODE
puts RubyVM::InstructionSequence.compile(code).disasm
输出的 YARV 指令如下:
== disasm: #<ISeq:<compiled>@<compiled>>================================
0000 trace 1 ( 1)
0002 putself
0003 opt_send_without_block <callinfo!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>
0006 putself
0007 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>
0010 opt_minus <callinfo!mid:-, argc:1, ARGS_SIMPLE>, <callcache>
0013 putobject_OP_INT2FIX_O_0_C_
0014 opt_gt <callinfo!mid:>, argc:1, ARGS_SIMPLE>, <callcache>
0017 branchunless 30
0019 trace 1 ( 2)
0021 putself
0022 putself
0023 opt_send_without_block <callinfo!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>
0026 opt_send_without_block <callinfo!mid:p, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0029 leave ( 1)
0030 trace 1 ( 4)
0032 putself
0033 putself
0034 opt_send_without_block <callinfo!mid:b, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>
0037 opt_send_without_block <callinfo!mid:p, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
0040 leave
Hmmm,虽然有点乱,但是连蒙带猜应该能明白是怎么回事。
0002 putself
- 把当前的 self 作为方法的接受者;
0003 opt_send_without_block <callinfo!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>
,发出名为a
的消息,0 个参数,不带块;
0010 opt_minus <callinfo!mid:-, argc:1, ARGS_SIMPLE>, <callcache>
发送名为 -
的消息,1 个参数,即做减法,opt_plus 或者 opt_minus 是优化后的专有 YARV 指令;
0014 opt_gt <callinfo!mid:>, argc:1, ARGS_SIMPLE>, <callcache>
比较大小;
然后重点来了!
0017 branchunless 30
,否则跳到第 30 行,这里有个 unless!
紧跟在这下面的指令是打印 a 的值:
0023 opt_send_without_block <callinfo!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>, <callcache>
0026 opt_send_without_block <callinfo!mid:p, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
虽然有个unless
在这里横着,可是“条件”和“结果”的顺序居然因为负负得正,又和我们人类写代码的顺序一样了,真是不可思议;不过 Ruby 是在 1.9 版本才有了 YARV...
嘛,没有结论,只是我该关上脑洞去睡觉了。