【问题标题】:How to organize Procs in Ruby?如何在 Ruby 中组织 Procs?
【发布时间】:2019-09-06 18:33:52
【问题描述】:

将脚本从 OOP 转换为 FP,我试图了解如何用“过程包”替换“方法包”(类)。 Procs 是那些可组合的(

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

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

尝试将 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 的改进。 请指教。


编辑

看起来我尝试使用 Hash 对 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

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

所以我获得了嵌套闭包(变量一直向下泄漏),但此时的代码与常规类没有太大区别。 #new 变成了#call(毕竟 Proc 继承自 Method),但在 FP 方向上并没有太大的改进。请指教。

【问题讨论】:

  • 为什么不直接使用类方法(比如其他语言中的“静态”方法)?
  • @maxpleaner 我认为 OP 希望函数具有函数组合运算符,而 Ruby 的方法并不完全支持
  • 您可能希望使用更简洁的 JavaScript 样式的哈希声明:{ start: ... } 而不是 { :start =&gt; ... },因为它从 Ruby 1.9 开始就已经存在并且现在得到完全支持。旧的 1.8 样式仅在使用字符串键之类的东西时才需要。
  • 我在这里写了一个关于从“FP”返回到 OOP 的答案,因为我不确定这里的目标是什么。正如您所说,这不是函数式编程,它只是一个“函数包”。 “函数式编程”通常指的是对于任何给定的参数集总是产生相同输出的函数,即它们是无状态的。使用闭包来封装状态会完全破坏它。你这里的风格不是“功能性的”,但它肯定是带有大量 JavaScript 风格的。
  • @tadman 绝对同意这不起作用,但它很接近并且不那么难修复。

标签: ruby functional-programming


【解决方案1】:

使用模块和常量。

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

您的函数应该是常量,这样可以防止您重新分配(哈希不会)。模块也不能被实例化/继承,所以这避免了任何类型的 OOP。另一个好处是可以包含模块,因此如果您希望某些函数成为顶级的上下文,您可以轻松地做到这一点。


编辑

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

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

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

【讨论】:

  • 要使其正常运行,您必须将所有内容都作为参数提供,bot 之类的东西不会神奇地出现。我不确定这种编程风格有什么好处(如果有的话)。
  • @Max 匿名样式不起作用:BotControl = Module.new do Start = proc {bot.run(:async)} end BotControl::Start.call 返回uninitialized constant BotControl::Start (NameError):为什么?
  • @WuMing2 我给出的示例不会使用 procs,因为 Ruby 不是这样工作的。 “在罗马时”原则适用于此。在 Ruby 中以 Ruby 方式进行操作。如果您想探索函数式编程,那是一次很棒的学习体验,但 Ruby 并不是最好的学习场所。 Erlang 以及受到 Ruby 启发并在 Erlang VM 中运行的扩展 Elixir 是两个很好的选择。
  • @tadman 看到不同语言的解决方案(例如 Julia)会很有趣。 IE。如何对函数进行分组,同时利用闭包而不进行封装。
  • Max 和 @tadman 感谢你们俩。不用我自己咕哝,这很好,也很有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-23
  • 2014-02-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-06
相关资源
最近更新 更多