作者godfat (godfat 真常)
看板Ruby
标题[Ruby] recursive lambda
时间Mon Apr 16 23:53:21 2007
附带连结版:1)
http://blog.godfat.idv.tw/2007/04/ruby-recursive-lambda.html
2)
http://lightyror.thegiive.net/2007/04/ruby-recursive-lambda.html
[Ruby] recursive lambda
==本文连同引文同步载於 ptt Ruby 板、LightyRoR、饱和脂肪星(星之一角备份区)==
很抱歉最近状况真的是相当糟糕,导致很多事情都没做或是没做好。虽然以後大概
也不会比较好。这样讲讲就没关系吗?当然不是,只是替自己找一点比较能安心的
藉口吧。另外本文有任何错误欢迎指出。
==本文开始==
我一直觉得 Ruby 缺少一个类似 self 的东西,用来表达现在这个 function/method.
这个东西有什麽用呢?其实我也不知道有什麽用,就只是单纯觉得好像少了这种东西。
最直觉的例子,恐怕就是具有递回能力的 lambda function. 我曾在 ptt Ruby 板
发过一篇文,讲 quine(self-reproducing programs),後来我用了 Ruby2Ruby,
写了像这样的结果:(饱和脂肪星有该文的备份(通常连不上))
#!/usr/bin/ruby
require 'rubygems'
require 'ruby2ruby'
(a = proc {
puts("#!/usr/bin/ruby")
puts
puts("require 'rubygems'")
puts("require 'ruby2ruby'")
puts
print("(a = ")
print(a.to_ruby)
print(").call")
}).call
最蠢的地方是明明都用 lambda(proc) 了,我却还得把 lambda 的结果记起来
留待以後使用。这样实在是有点无趣。我希望我可以写:
lambda{ print(this.to_ruby); print(".call") }.call
这样不是帅气多了吗?於是我开始试着思索实作这东西的可能。接着我忽然想到,
所谓 this 不正是指在 call stack 最上端的 function/method 吗?因为当我们
执行到这个 function/method 时,this 一定是指同一 function, 不可能忽然去
指涉其他 function, 而另外一个 function 进 call stack 时,不把他解掉也
不可能会执行到 this. 於是可以把 this 写成一个 function, 不吃任何引数,
回传一个 Proc/Method 代表正在 call stack 顶端的那个 function.
而我记得 Ruby 是有方法可以去存取 call stack... 虽然好像是用很蠢的方法,
也确实是有点蠢,但总之可以用模拟的。随意 google 了一下,找到一个很简单的
方式,就是用 set_trace_func, 丢一个 callback 进去,於是 Ruby 在各个
function 间做动作的时候,都会呼叫这个 callback. 感觉就是效率会变狂差,
不过呢,至少暂时是可以用的。
接着可以利用 Thread.current[:symbol] 来储存 current thread call stack info,
任何 function call 时,push 资料进这个假的 call stack, function return 时,
pop 资料出来。这样就有一个很简单的 call stack info 可以用了。
以下程式欢迎任意使用,licensed under Apache License 2.0, 复制到档案用的话
希望可以把以下这段 copy 到档案最前面… XD
# Copyright (c) 2007, Lin Jen-Shin(a.k.a. godfat 真常)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
module Kernel
# 由於 google 到的参考程式把抓 call stack 叫 invoker,
# 所以这里沿用他的名字。有个常数比较容易看懂程式在做什麽。
INVOKER_EVENT = 0
INVOKER_FILE = 1
INVOKER_LINE = 2
INVOKER_MSG = 3
INVOKER_BINDING = 4
INVOKER_CLASS = 5
# -1 就是 top 的意思罗
def invoker levels = -1
st = Thread.current[:callstack]
# st 有可能是 nil, 如果 invoker 先被 call 到的话。
# 虽然我不知道什麽时候会发生这种事…。levels - 2 的原因是:
# 0(stack bottom) => function that you called(this is what we want)
# 1 => Kernel#invoker
# 2 => Array #[]
# 所以去掉额外不要的额外两个资讯。
st && st[levels - 2]
end
def this
# 因为多 call 了 this, 所以要再多去掉一个额外资讯。
info = invoker(-2)
# 这边我本来写成 lambda 的形式,可以正确执行,但有一个状况
# 却是失败的。就是 lambda{yield}.call{} 这样是会 error 的 :(
# 试了多次还是找不到 Proc.call 吃 block({}) 的方法,只好改写
# 成用 Method 的形式。不知为何,Method 就可以正确使用 block...
eval("self", info[INVOKER_BINDING]).method(info[INVOKER_MSG])
end
end
set_trace_func lambda{ |*args|
case args[INVOKER_EVENT]
# 可能的有 call 和 c-call, 都是 function
when /call$/
# 这边我搞不清楚到底是谁会先被 call, 是 call 还是 return?
# google 来的是把初始化写在 call 里,可是我测试都显示是在
# return 上,所以反而是 return 的地方需要初始化。或是乾脆
# 全部拉出来在最上面初始化也可以 :)
(Thread.current[:callstack] ||= []).push args
# 同上可能有 return 和 c-return
when /return$/
(Thread.current[:callstack] ||= []).pop
end
}
以上不管有没有问题,都可以来看一下我额外写的几个 unit test, 参考一下
几个我目前想到的用法。
require 'test/unit'
class TestThis < Test::Unit::TestCase
def test_fact
assert_equal(120, fact(5))
assert_equal(3628800, fact(10))
# 试用 recursive lambda
assert_equal(5040, lambda{|n| return n*this[n-1] if n>0; 1}[7])
end
def fact n
# 恐怕是最常见的用法
return n*this[n-1] if n > 0
1
end
##
def test_pass_around
# 这边流程可能很怪,因为只是我随便写的,单纯测试正确性罢了。
assert_equal(method(:pass_around_forward), pass_around.call(lambda{|v| v}))
end
def pass_around mode = 'pass'
case mode
when 'pass'
pass_around_forward this
else
'value'
end
end
def pass_around_forward func
assert_equal('value', func['value'])
this
end
##
def test_with_block
# 同上,流程乱写的,单纯测试正确性。
with_block{|b| assert_equal('value', b['value'])}
end
def with_block mode = 'pass', &block
case mode
when 'pass'
block[this]
else
'value'
end
end
##
def test_more_args
# Proc 就是死在这个测试,block 展开怎麽做都失败 :(
# 改成 Method 後这边就可以通过测试了。
more_args('get_this'){}.call('call', 1, 2, 3, 4, 5, &lambda{6})
more_args('get_this'){}.call('call', 1, 2, 3, 4, 5){6}
end
def more_args mode, a1=nil, a2=nil, a3=nil, *as, &block
case mode
when 'get_this'
this
else
assert_equal(1, a1)
assert_equal(2, a2)
assert_equal(3, a3)
assert_equal(4, as[0])
assert_equal(5, as[1])
assert_equal(nil, as[2])
assert_equal(6, yield)
assert_equal(6, block.call)
end
end
end
最後,我可以把 quine 改写成:
#!/usr/bin/ruby
require 'rubygems'
require 'ruby2ruby'
require 'invoker'
def f
puts("#!/usr/bin/ruby")
puts
puts("require 'rubygems'")
puts("require 'ruby2ruby'")
puts("require 'invoker'")
puts
print(this.to_ruby)
print("\nf")
end
f
为什麽要用 def f; end 呢??这样不就失去 this 的意义了??
因为不晓得为什麽,Ruby2Ruby 跑 lambda + this 都会有奇怪的 runtime error,
而这个错误是来自 class RubyToRuby 的 rewrite_defn(exp), 最後的:「
raise "Unknown :defn format: #{name.inspect} #{args.inspect} #{body.inspect}"
」这我一时不知道要怎麽解决,不知道会是谁的错,所以只好暂时用 def f; end 这种
蠢方式了。
另外,还有一些无聊的花枝可以玩:
def just_print_it n
print n
this
end
然後就可以:
just_print_it(1)[2][3][4][5]
输出:12345
def add_attr attr
($attr ||= []).push attr
this
end
add_attr('hello')['world']['and then?']['good-bye']
或是很简单的流程控制:
def fight step = :ready
case step
when :ready
ready this
when :go
go this
when :finish
finish this
end
end
def ready callback
play_ready_animate
if blah
callback[:go]
else
callback[:finish]
end
cleanup_ready
end
def go callback
gogogo
if blah
callback[:finish]
else
callback[:ready]
end
cleanup_go
end
类推,我也不知道有什麽用,搞不好有什麽 cleanup 是要等所有的事都做完才能做?
总而言之,就是这样啦,没什麽营养,但我却觉得很重要的功能。在很多时候可以少打
很多字。搞不好哪一天也会想到什麽真的很有价值的应用也说不定。总觉得真的有太多
事是无心插柳柳橙汁。虽然大部份都来不及喝就乾掉了。
2007.04.16 godfat 真常
--
生死去来、棚头傀儡、一线断时、落落磊磊
《花镜》-世阿弥
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 220.134.30.220