Ruby 板


LINE

睽违已久,忽然心血来潮多加了几个东西。 in CHANGES: ============================== ludy 0.0.3, 2007.08.07 1. ludy_ext: added: 1. Proc#curry 2. Proc#compose 3. Proc#chain 4. Symbol#to_proc 5. Array#foldl 6. Array#foldr 7. Array#filter removed: 1. Fixnum#collect # see tc_ludy_ext.rb#test_fixnum_collect for reason info: 1. ruby2ruby has NilClass#method_missing return nil, so i can't just make it return blackhole 2. module Curry: see test/tc_curry.rb for usage see unit test for usage ============================== 虽然说请看 unit test 来揣摩用法,不过这样真的有点无趣, 所以还是来稍微介绍一下。这次之所以忽然心血来潮想做,是因为看到 James Edward Gray II 的 higher-order ruby 专栏: http://blog.grayproductions.net/articles/category/higher-order-ruby 第六篇的:Currying, 他的 curry 实做: class Proc def curry(&args_munger) lambda { |*args| call(*args_munger[args]) } end end 老实讲,不是说看不懂,可是我不明白为什麽要写得那麽复杂,乍看之下实在看不太出来。 丢掉他的实做,我试着做了一个: class Proc def curry *pre lambda{ |*post| self[*(pre + post)] } end end 就我自己测试起来,效果是一样的,我认为应该简洁易懂多了。 可是这根本不太像 curried function 吧?内心呐喊着。 不过在看我後来写的 curry module 之前,先来简单介绍一下 currying. 在 lambda caculus 中,每个 function 都只能有一个 argument, 这是因为 lambda caculus 是一种极简的语言,用来研究某些模型的语言。 但是如果 function 只能吃一个 argument, 有很多事是会做不到的。 於是我们可以靠着 tuple 把许多的 argument 包成一个 argument, 例如在 Haskell 中,tuple 就是 (1,2,3), 用括号括起来的就是 tuple. 所以上面的 (1,2,3) 是一个有三个值的 tuple, 可以把他视为一个值。 比方说有个 function 叫 power, 像是:power 2, 10 会回传 1024. uncurried function 就会是 power (2, 10), 他吃一个有两个元素的 tuple, 吐出一个 1024 的值。可是如果是这样使用的话,其实是很不方便的。有一个 方法可以让 function 依然只吃一个 argument, 但是又不需要使用 tuple, 可以一个值一个值传入,那就是 curried function. 在 functional programming 中,function 的地位极高,不管在做什麽事, 几乎都是在操作 function. 这也就是所谓的 higher-order function, 操作 function 的 function, 或是产生 function 的 function 诸如此类。 curried function 的效果就是当所吃入的 argument 不足时,他会再吐出另一个 function 去吃其他 argument,直到 argument 足够时才会吐出结果。 power 2 的回传会是一个 function, 他记住了 2, 当他再吃一个 argument 後, 则会再把 2 拿出来跟 argument 做运算。所以 power 的 type 会是: power :: Int -> Int -> Int 结合顺序是从右边开始,所以是吃一个 Int, 吐出 (Int -> Int), 也就是吃一个 Int 吐出一个 Int 的 function. 可以想成这样: power2 = power 2 result = power2 10 result # => 1024 也就是说,可以把他看成是一个会不断记忆 argument 的 function. 看看 James Edward Gray II 的范例: multiply = lambda { |l, r| l * r } double = multiply.curry { |args| args + [2] } triple = multiply.curry { |args| args << 3 } multiply[5, 2] # => 10 double[5] # => 10 triple[5] # => 15 triple["Howdy "] # => "Howdy Howdy Howdy " 所以他的实做其实很简单,就是用一个 array 记忆 arguments, 最後再 prepend 到最後的 arguments 里。 class Proc def curry(&args_munger) lambda { |*args| call(*args_munger[args]) } end end 不过我觉得不用写得那麽复杂,所以改写为: class Proc def curry *pre lambda{ |*post| self[*(pre + post)] } end end 这里利用了 ruby 的 lambda 有 closure 的特性,把 *pre 纪录 下来,再把他 prepend 到 post 上,最後再呼叫原本的自己(self)。 可是这样不完整,因为你必须明确表达你需要做 curry, 而 Haskell 的 curried function 是可以让你忽略这件事的。 lambda{|a,b,c,d,e|}.curry(1).curry(2).curry(3).curry(4).carry(5) 这样不烦死才怪。我希望能用: lambda{|a,b,c,d,e|}[1][2][3][4][5] 也能使用: lambda{|a,b,c,d,e|}[1,2][3][4,5] 可惜我暂时还没找到好做法 XD 目前暂时仅提供可以 mixin 的 module, 大概是这样用: class Array; include Curry; end 接着 array 所有以字母开头的 method 会多个 curried 版, prefix c. i.e., map => cmap; foldr => cfoldr func1 = [1,2,3].cfoldr[:-.to_proc] assert_equal 2, func1[0] 做法其实很简单,就只是检查参数够了没,不够就重新 curry 一份, 够了就呼叫原始 method. 我原本一直不希望前缀 c, 而以相同名字命名之, 然後原本的名字改为:orig_method. 可惜不管怎麽试都失败,原因不是很清楚, 但这种 side-effect 超大的动作,会失败其实也不怎麽奇怪吧,我想。 虽然我总觉得以正常呼叫法而言,应该是没什麽差才对,也许我有哪里写错了, 只是还没发现而已。 * 至於其他新增的东西,这里也稍微介绍一下。首先 Array#filter 只是 select 的 alias, Array#foldl 也只是 inject 的 wrapper. Array#foldr 稍微复杂些, 不过概念上只是类似反过来的 inject 而已。Symbol#to_proc 大家应该都很熟, 连 active_support 里面也有。只是单纯的 message/method 转换而已。 比较需要提的应该是 Proc#compose 和 Proc#chain. 前者就是数学上的 compose, facets 里其实也有,不过手痒还是自己做了一份。 他有点类似反向的 inject, 很容易做出来。至於 chain, 这是模仿 C++ 的 loki 中的 functor 中的 chain. 效果很单纯,就是把 function 串起来而已。 这拿来做 callback 应该还算方便,例如: button.on_click = menu.method(:popup).chain button.method(:hide) 接着当按钮被按下去後,选单就会弹出,且按钮自动隐藏。 至於 arguments 和 returns 呢?arguments 会统一给所有人。 f1.chain(f2)['XD'] 这样 f1 和 f2 都会接到 'XD' 这个 argument. return 则会蒐集成一个 array 并 flatten 回去。 [f1 的结果, f2 的结果, f3 的结果,...] 如果 f3 的结果是 array, 则会依序储存: [..., f3 的结果1, f3 的结果2, f4 的结果, ...] 这样做的原因是要让 chain 还能继续 chain 而不会出现非常恐怖的 nested array. f1.chain(f2).chain(f3).chain(f4) 但是其实可以这样 chain: f1.chain(f2, f3, f4) 结果和上面的会是相同的。 在 chain 之间的 travel 还没做,下次有机会时会做。 gem install ludy # to see detail ruby 写起来真的很简洁,很多功能 10 行内都能解决。 2007.08.07 -- 「行け!Loki!」(rocky ロッキー) -Gurumin ぐるみん 王子? XD --



※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 61.218.90.242







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灯, 水草

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

TOP