Ruby 板


LINE

附带连结版: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







like.gif 您可能会有兴趣的文章
icon.png[问题/行为] 猫晚上进房间会不会有憋尿问题
icon.pngRe: [闲聊] 选了错误的女孩成为魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一张
icon.png[心得] EMS高领长版毛衣.墨小楼MC1002
icon.png[分享] 丹龙隔热纸GE55+33+22
icon.png[问题] 清洗洗衣机
icon.png[寻物] 窗台下的空间
icon.png[闲聊] 双极の女神1 木魔爵
icon.png[售车] 新竹 1997 march 1297cc 白色 四门
icon.png[讨论] 能从照片感受到摄影者心情吗
icon.png[狂贺] 贺贺贺贺 贺!岛村卯月!总选举NO.1
icon.png[难过] 羡慕白皮肤的女生
icon.png阅读文章
icon.png[黑特]
icon.png[问题] SBK S1安装於安全帽位置
icon.png[分享] 旧woo100绝版开箱!!
icon.pngRe: [无言] 关於小包卫生纸
icon.png[开箱] E5-2683V3 RX480Strix 快睿C1 简单测试
icon.png[心得] 苍の海贼龙 地狱 执行者16PT
icon.png[售车] 1999年Virage iO 1.8EXi
icon.png[心得] 挑战33 LV10 狮子座pt solo
icon.png[闲聊] 手把手教你不被桶之新手主购教学
icon.png[分享] Civic Type R 量产版官方照无预警流出
icon.png[售车] Golf 4 2.0 银色 自排
icon.png[出售] Graco提篮汽座(有底座)2000元诚可议
icon.png[问题] 请问补牙材质掉了还能再补吗?(台中半年内
icon.png[问题] 44th 单曲 生写竟然都给重复的啊啊!
icon.png[心得] 华南红卡/icash 核卡
icon.png[问题] 拔牙矫正这样正常吗
icon.png[赠送] 老莫高业 初业 102年版
icon.png[情报] 三大行动支付 本季掀战火
icon.png[宝宝] 博客来Amos水蜡笔5/1特价五折
icon.pngRe: [心得] 新鲜人一些面试分享
icon.png[心得] 苍の海贼龙 地狱 麒麟25PT
icon.pngRe: [闲聊] (君の名は。雷慎入) 君名二创漫画翻译
icon.pngRe: [闲聊] OGN中场影片:失踪人口局 (英文字幕)
icon.png[问题] 台湾大哥大4G讯号差
icon.png[出售] [全国]全新千寻侘草LED灯, 水草

请输入看板名称,例如:WOW站内搜寻

TOP