在Ruby中队列而不是方法链和规则而不是条件

时间:2014-01-29 05:23:58

标签: ruby haskell clojure paradigms

Rich Hickey在他的演讲Simple Made Easy中描述了Clojure和Haskell的范例。作为一个ruby / rails程序员(这是我真正知道的),我喜欢他的想法,但不理解他们中的两个:

  • 使用队列,而不是方法链接
  • 规则而不是条件

改为使用队列

显然,在Rails中我们喜欢方法链接,但我想了解一下Queue在Ruby中的描述方式(视频中的54:54):

  

如果事物A调用了事物B,那么你只需要它。你有什么时间和地点。 A必须知道B在哪里才能调用B.当发生这种情况时,无论什么时候发生都是在A做的时候。把队列粘在那里。

规则与条件

他谈到不使用条件语句或转换语句而是使用规则(视频中的30:00)。

对于Ruby来说,我根本就不了解。如何在不使用条件的情况下做出决定?

谢谢大家, 贾斯汀

3 个答案:

答案 0 :(得分:16)

你好,队列

这里的想法是,我们可以通过在它们之间插入队列来解耦它们,而不是将值直接从一个对象传递给另一个对象。

假设我们正在模拟一位农民从鸡肉中采集鸡蛋。鸡生产鸡蛋,农民收集它们。当他们收集了五个鸡蛋时,农民的转变就完成了。通常,我们可能会这样写:

class Chicken
    def initialize(name)
            @name = name
    end

    def lay_egg
            sleep random(3)
            "an egg from #{@name}"
    end
end

class Farmer
    def initialize(name, chicken)
            @name           = name
            @chicken        = chicken
    end

    def work_shift
            5.times do
                    egg = @chicken.lay_egg
                    puts "#{@name} got #{egg}"
            end
    end
end

betsy       = Chicken.new "Betsy"
fred        = Farmer.new "Fred", betsy
fred.work_shift

因此,农民等待鸡肉,并在他们来的时候捡到鸡蛋。很好,问题解决了,去冰箱喝啤酒。但是,如果我们买了第二只鸡来增加鸡蛋产量呢?或者,如果我们想通过让他们从纸箱中捡到鸡蛋来测试我们的农民的灵活性呢?

因为我们已经将农民编码为需要鸡肉,所以我们失去了做出这些决定所需的灵活性。如果我们能够将它们分离,我们就会有更多的自由。

所以,让我们在它们之间插入一个队列。鸡会在滑道顶部产卵;农民将从滑槽底部收集鸡蛋。任何一方都不直接依赖另一方。在代码中,可能如下所示:

class Chicken
    def initialize(name, chute)
            @name   = name
            @chute  = chute
            Thread.new do
                    while true
                            lay_egg
                    end
            end
    end

    def lay_egg
            sleep rand(3)
            @chute << "an egg from #{@name}"
    end
end

class Farmer
    def initialize(name, chute)
            @thread = Thread.new do
                    5.times do
                            egg = chute.pop
                            puts "#{name} got #{egg}"
                    end
            end
    end

    def work_shift
            @thread.join
    end
end

chute       = Queue.new
betsy       = Chicken.new "Betsy", chute
fred        = Farmer.new "Fred", chute
fred.work_shift

除了现在,我们可以轻松添加第二只鸡。这是梦想的东西:

chute       = Queue.new
betsy       = Chicken.new "Betsy", chute
delores     = Chicken.new "Delores", chute
fred        = Farmer.new "Fred", chute
fred.work_shift

你可以想象我们也可以用一堆鸡蛋来装载一个滑道来测试农民。不需要嘲笑鸡,我们只是准备一个队列并传递它。

再见,条件

我对此的回答可能有点争议,但要短得多。你可以看一下multimethods in Ruby,但这个想法的关键是放弃封闭的,硬编码的逻辑路径,而不是开放的逻辑路径,事实上,普通的多态性就是这样。

每当你调用某个对象的方法而不是打开它的类型时,你就会利用Ruby的基于类型的规则系统而不是硬编码逻辑路径。显然,这个:

class Dog
end

class Cat
end

class Bird
end

puts case Bird.new
when Dog then "bark"
when Cat then "meow"
else "Oh no, I didn't plan for this"
end

不如此开放:

class Dog
    def speak
            "bark"
    end
end

class Cat
    def speak
            "meow"
    end
end

class Bird
    def speak
            "chirp"
    end
end

puts Bird.new.speak

在这里,多态性为我们提供了一种描述系统如何使用不同数据行为的方法,这些数据允许我们随心所欲地为新数据引入新行为。所以,干得好,你(希望)每天都避免条件限制!

答案 1 :(得分:5)

这两点都没有被Haskell很好地体现。我认为Haskell仍然导致一些未完成的代码,但是用不同的哲学和不同的工具来解决整个问题。

<强>队列

粗略地说,Hickey想指出如果你在一个调用另一个对象的对象上编写一个方法

class Foo
  def bar(baz)
    baz.quux
  end
end

然后我们刚刚硬编码了传入Foo#bar的内容必须有quux方法的概念。这是他的观点的一个比较,因为它意味着Foo的实现本质上与实现传递给Foo#bar的对象的实现有关。

这在Ruby中不是一个问题,其中方法调用更像是在对象之间发送的动态调度消息。它只是意味着传递给Foo#bar的对象在给出quux消息时必须以某种方式负责任地响应,而不是更多。

但它确实意味着消息处理的顺序性。如果您将消息发送到队列中以最终传递给结果对象,那么您可以轻松地将中间人放置在该接缝处 - 也许您希望同时运行barquux

除了Haskell之外,这个想法在Erlang中被视为一个逻辑极端,我强烈建议学习Erlang如何解决这些问题。

 spawn(fun() -> Baz ! quux)

<强>规则

Hickey反复强调,特殊的,硬编码的分支方法可以收集东西。要指出,他不喜欢案例陈述或模式匹配。相反,他建议规则,我认为它是指“生产规则”系统。它们通过允许程序员为某些操作“触发”然后等待直到传入事件满足足以引发操作的规则来设置一组规则来产生选择和分支。这些想法最着名的实现是Prolog。

Haskell将模式匹配深深地融入其灵魂之中,因此很难说Haskell会立即以这种方式进行完成...但是在Haskell- 类型类分辨率中存在一个非常好的规则系统示例

可能最着名的概念是mtl - 样式的类型类,你最终会用

这样的签名来编写函数
foo :: (MonadReader r m, MonadState s m, MonadIO m, MonadCatch m)
    => a -> m b

其中foom类型中是完全多态的,只要它遵循某些约束 - 它必须有一个常量上下文r,一个可变的上下文s,能够执行IO,以及抛出和捕获异常的能力。

哪些类型实例化所有这些约束的实际解决方案是通过(喜欢或其他)称为“类型类prolog”的规则系统来解决的。实际上,它是一个足够强大的系统来编码类型系统内的整个程序

它实际上非常好,并为Haskell提供了一种自然的依赖注入风格,如上面的mtl示例所述。

但是,我认为,在长时间使用这样的系统之后,大多数Haskeller都明白,虽然规则系统有时很聪明......但它们也很容易失控。 Haskell有一个很多对类类prolog的强大限制,这确保了程序员很容易预测它将如何解决。

这对于整个规则系统来说是一个主要问题:你失去了对最终解雇的行为的明确控制......所以按摩你的规则来实现你期望的那种结果变得更加困难。我实际上并不确定我同意Rich这里规则系统因此导致了解包。您可能没有明确地分离与其他对象相关的信息,但是您在事物之间设置了许多模糊的,远程的依赖关系。

答案 2 :(得分:1)

队列

使用队列表示将程序拆分为多个进程。例如,一个只接收电子邮件并将其推入“处理”队列的进程。另一个进程从“处理”队列拉出并以某种方式转换消息,放入“传出”队列。这样可以轻松更换某些部件而无需接触其他部件。如果性能不好,您甚至可以考虑使用其他语言进行处理。如果你写e=Email.fetch; Processor.process(e),你就结合所有进程。

队列的另一个优点是可能有许多生产者和消费者。只需添加更多“处理”流程(使用线程,其他机器等),您就可以轻松地“扩展”处理部分。另一方面,您也可以启动更多“电子邮件提取程序”进程。如果你在一次通话中全部收集,这很复杂。

ruby​​中有一个简单的队列http://ruby-doc.org/stdlib-1.9.3/libdoc/thread/rdoc/Queue.html 和许多其他人(rabbitmq,db-based等)

规则

规则使代码变得简单。而不是if-then-else,我们鼓励您创建规则。看一下clojure core.match lib:

(use '[clojure.core.match :only (match)])

(doseq [n (range 1 101)]
  (println
    (match [(mod n 3) (mod n 5)]
      [0 0] "FizzBuzz"
      [0 _] "Fizz"
      [_ 0] "Buzz"
      :else n)))

你可以写if(mod3.zero?&amp;&amp; mod5.zero?)else if ....但它不会那么明显,而且(更重要的是)很难添加更多规则。

对于ruby,请查看https://github.com/k-tsj/pattern-match,尽管我没有在ruby中使用这些库。

<强>更新

在他的演讲中,Rich提到可以使用类似prolog的系统来替换条件和规则。 core.match并不像prolog那么强大,但它可以让你了解如何简化条件。