如何在Ruby中组织Procs?

时间:2019-09-03 16:30:26

标签: ruby functional-programming

将脚本从OOP转换为FP,我试图了解如何将“方法包”(类)替换为“过程包”。 Procs是具有Binding的那些可组合(<<),匿名微对象,但是我没有看到使用多个#call的示例。我谦虚的脚本只有几十种方法:用proc替换它们需要某种容器来组织代码。

试图将Procs存储到哈希中。但是Hash在设计时并未考虑到此应用程序。例如,从同一散列内的另一个proc调用proc是很尴尬的。

试图将Procs存储到与变量关联的类中。但是封装降低了我访问外部变量的能力(失去了闭包的一项主要好处),并使对proc的访问变得复杂(使用实例变量必须实例化类,或者使用类变量必须创建类访问器方法)。

试图将Procs存储到方法中,但是获得的好处是什么。


require 'net/http'
require 'json'
require 'iron_cache'
require 'discordrb'

class Config
      # config vars
end

data_space = {
    :message => ""
    }

bot = Discordrb::Bot.new(token: Config::DISCORD_TOKEN, ignore_bots: true)
channel = bot.channel(Config::DISCORD_CHANNEL_ID)
cache = IronCache::Client.new.cache("pa")

bot_control = {
    :start => proc {bot.run(:async)},
    :stop => proc do
        bot.stop
        exit(0)
    end,
    :send => proc {data_space[:message].then {|m| bot.send_message(channel, m)}},
    "messaging?" => proc do |wish|
        [ Config::MESSAGING[wish.first] , Config::NIGHTTIME_FLAG ].compact.first
    end
}

cache_control = {
    :get_flag => proc {|flag| cache.get(flag)&.value},
    :set_wish => proc do |wish, exp_sec|
        cache.put("messaging", wish.to_json, :expires_in => exp_sec)
        data_space[:message] << "#{wish.first} got it!"
    end,
    :exp_sec => proc do
        t = Time.now
        (t-Time.new(t.year,t.month,t.day,Config::EXP_REF,0,0)).to_i
    end,
    :get_wish => proc do
        msg = proc {cache_control[:get_flag].call("messaging")}
        msg ? JSON.parse(msg) : [nil, nil]
    end
}
bot_control[:start].call
data_space[:message] << "ah"
bot_control[:stop].call if (bot_control["messaging?"]<<cache_control[:get_wish]).call
(bot_control[:stop]<<bot_control[:send]).call

实际交付。

但是我认为不是可读的那种代码。还是对OOP的改进。 请告知。


编辑

看起来我尝试使用哈希将proc分组的方法是正确的。如elsewhere所述,Proc中的哈希将实例方法添加为#count,#keys和#store来访问和操作它。因此,我可以这样写:


bc = proc { |args|
  {
    start: proc {...},
    stop: proc {...}
  }
}

bot_control = bc.call
bot_control[:start].call
bot_control[:stop].call

有一些警告。哈希返回的值是proc,并且添加了组合方法(<<和>>),但它们不起作用。另外,我还没有找到如何从哈希中引用其他值。尽管可以添加方法(默认情况下为私有,但可以声明为公共),并可以从值中引用它们。

因此,我获得了嵌套的闭包(变量一直向下泄漏),但此时的代码与常规类没有太大的不同。 #new已成为#call(毕竟Proc从Method继承),但是FP方向没有太大改进。请指教。

1 个答案:

答案 0 :(得分:2)

使用模块和常量。

module BotControl
  Start = proc {bot.run(:async)}
  Stop = proc do
      bot.stop
      exit(0)
  end
  Send = proc {DataSpace::Message.then {|m| bot.send_message(channel, m)}}
  Messaging = proc do |wish|
      [ Config::MESSAGING[wish.first] , Config::NIGHTTIME_FLAG ].compact.first
  end
end

请注意,此代码无法按原样工作:bot变量不可见!有两种方法可以解决此问题,但它们不在此问题的范围内(我的2¢,它应该是这些函数的参数)。

用法看起来像

(BotControl::Messaging<<CacheControl::GetWish).call

您的函数应该是常量,因此可以保护您免于重新分配(散列不会)。同样,模块不能被实例化/继承,因此避免了任何类型的OOPness。另一个好处是可以包含模块,因此,如果您希望将某些功能置于顶层,则可以轻松地做到这一点。


编辑

您可能会想使用匿名模块来做到这一点,例如

BotControl = Module.new do
  Start = proc {bot.run(:async)}
  ...
end

但是这是行不通的,因为Ruby中常量的范围是 lexical ,即在 parsing 阶段而不是在运行时,它们位于哪个模块很重要。在上面的示例中,在解析器级别,Start只是顶级,因为它不在任何模块内(Module.new只是一个方法调用,它在运行时进行评估)。因此,这种方法实际上并没有将任何东西组合在一起。