Очереди вместо цепочки методов и правила вместо условных выражений в Ruby

Рич Хикки описывает парадигмы Clojure и Haskell в своем докладе Simple Made Easy. Как программисту ruby/rails (это все, что я действительно знаю), мне нравились его идеи, но я не понял 2 из них:

  • Использование очередей, а не цепочки методов
  • Правила вместо условий

Использование очередей

Очевидно, что в Rails нам нравится цепочка методов, но я хотел понять, как будет выглядеть Queue в Ruby, как он это описал (54:54 в видео):

Если вещь А называет вещь Б, вы только что завершили ее. У вас есть когда и где вещь. A должен знать, где находится B, чтобы позвонить B. Когда это происходит, когда это происходит, когда A делает это. Вставьте очередь туда.

Правила и условия

Он говорит не об использовании условных выражений или операторов переключения, а о правилах (30:00 в видео).

Этого я просто не понимаю с точки зрения Ruby. Как принимать решения без использования условных выражений?

Спасибо всем, Джастин


person nitsujri    schedule 29.01.2014    source источник
comment
Как Haskell или Clojure вписываются во все это?   -  person Thomas M. DuBuisson    schedule 29.01.2014
comment
Доклад @ThomasM.DuBuisson Хики основан на парадигмах, которые он создал для Clojure, и на вещах, которые ему понравились в Haskell. Надеялся на понимание той стороны сообщества, которая работает с его учением.   -  person nitsujri    schedule 29.01.2014
comment
Вы подразумеваете условия из правил, вместо того, чтобы писать условия напрямую.   -  person Vektorweg    schedule 29.01.2014
comment
Я не считаю это в первую очередь мнением. Вопрос не в том, чтобы узнать мнение об утверждениях Рича, а в том, как реализовать их на конкретном языке. Возможно, он больше подходил бы для programmers.stackexchange.com. В любом случае, у него есть хорошие ответы и фантастические примеры от @Beyamor. Голосование за открытие.   -  person A. Webb    schedule 29.01.2014
comment
Ваш заголовок отредактирован с «Как программировать с простотой», который требует мнения, на конкретные вопросы, которые у вас есть.   -  person A. Webb    schedule 29.01.2014


Ответы (3)


Привет, Очереди

Идея здесь в том, что вместо того, чтобы передавать значение напрямую от одного объекта к другому, мы можем разделить их, вставив между ними очередь.

Допустим, мы моделировали фермера, собирающего яйца у курицы. Курица производит яйца, фермер их собирает. Смена фермера заканчивается, когда он собрал пять яиц. Обычно мы могли бы написать что-то вроде этого:

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

Вы можете себе представить, как мы могли бы также, скажем, загрузить в желоб кучу яиц, чтобы проверить фермера. Не нужно издеваться над курицей, мы просто готовим очередь и передаем ее.

До свидания, условности

Мой ответ на это может быть немного более спорным, но намного короче. Вы могли бы взглянуть на мультиметоды в 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

Здесь полиморфизм дал нам средство описания того, как система ведет себя с различными данными, что позволяет нам по прихоти вводить новое поведение для новых данных. Итак, отличная работа, вы (надеюсь) избегаете условных выражений каждый день!

person Beyamor    schedule 29.01.2014
comment
Пример Hello, Queues — это очень четкое объяснение концепции разделения через очереди, но за счет другого аспекта: ориентации на ценность. Общая очередь, доступная по ссылке, наследует проблемы параллелизма, о которых предупреждали в докладе. Ценностно-ориентированный подход будет вызывать функции, которые берут очередь и возвращают новую очередь с большим или меньшим количеством яиц в зависимости от функции. Этот подход сделал бы его последовательным решением, поскольку q должен передаваться/с каждым объектом, который на него влияет. Я хотел бы знать, что такое шаблон функционального программирования для решения этой проблемы. - person Teo Sartori; 04.04.2015

Ни один из этих двух пунктов не воплощен в Haskell очень хорошо. Я думаю, что Haskell по-прежнему приводит к несколько незавершенному коду, но подходит к проблеме в целом с другой философией и другими инструментами.

Очереди

Грубо говоря, Хикки хочет указать, что если вы пишете метод для объекта, который вызывает другой объект

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

тогда мы только что жестко закодировали представление о том, что все, что передается в Foo#bar, должно иметь метод quux. С его точки зрения, это завершение, потому что это означает, что реализация Foo неразрывно связана с реализацией того, как реализуется объект, переданный Foo#bar.

Это меньше проблема в Ruby, где вызов метода гораздо больше похож на динамически отправляемое сообщение, отправляемое между объектами. Это просто означает, что объект, переданный Foo#bar, должен каким-то образом ответственно реагировать на сообщение quux, не более того.

Но это подразумевает последовательность в обработке сообщений. Если вы вместо этого отправляете сообщение в очередь, чтобы в конечном итоге быть доставленным к результирующему объекту, вы можете легко разместить посредника в этом шве — возможно, вы захотите запустить bar и quux одновременно.

В Эрланге эта идея доведена до логической крайности больше, чем в Хаскеле, и я настоятельно рекомендую узнать, как Эрланг решает подобные проблемы.

 spawn(fun() -> Baz ! quux)

Правила

Хикки неоднократно подчеркивает, что конкретные, жестко закодированные методы выполнения ветвления создают комплекс. К слову, ему не нравится оператор case или сопоставление с образцом. Вместо этого он предлагает Правила, под которыми, как я полагаю, он подразумевает системы «производственных правил». Они обеспечивают выбор и разветвление, позволяя программисту установить набор правил для того, когда определенные действия «запускаются», а затем ждать, пока входящие события не удовлетворят достаточным правилам, чтобы вызвать срабатывание действий. Наиболее известной реализацией этих идей является Пролог.

В Haskell сопоставление с образцом встроено глубоко в его душу, поэтому трудно утверждать, что Haskell сразу же выполняет декомплементацию таким образом... но в Haskell есть действительно хороший пример системы правил — разрешение классов типов.

Вероятно, наиболее известным понятием этого являются классы типов в стиле mtl, где вы в конечном итоге пишете функции с сигнатурами, такими как

foo :: (MonadReader r m, MonadState s m, MonadIO m, MonadCatch m)
    => a -> m b

где foo полностью полиморфен в типе m, если он соответствует определенным ограничениям — он должен иметь постоянный контекст r, изменяемый контекст s, возможность выполнения IO и возможность генерировать и перехватывать исключения.

Фактическое разрешение того, какие типы реализуют все эти ограничения, решается системой правил, которую часто (любезно или иначе) называют «прологом класса типов». Действительно, это достаточно мощная система, чтобы кодировать целые программы внутри системы типов.

Это на самом деле очень приятно и дает Haskell своего рода естественный стиль внедрения зависимостей, как описано в примере mtl выше.

Тем не менее, я думаю, что после длительного использования такой системы большинство хаскеллеров приходят к пониманию того, что, хотя системы правил иногда бывают умными... они также могут легко выйти из-под контроля. В Haskell есть множество осторожных ограничений мощности пролога класса типов, которые гарантируют, что программисту будет легко предсказать, как он разрешится.

И это основная проблема систем правил в целом: вы теряете явный контроль над тем, какие действия в конечном итоге будут запущены... так что становится сложнее корректировать ваши правила для достижения ожидаемого результата. На самом деле я не уверен, что согласен с Ричем в том, что системы правил, таким образом, ведут к декомплементации. Возможно, вы не разветвляете явно информацию, связанную с другими объектами, но вы устанавливаете множество нечетких долгосрочных зависимостей между вещами.

person J. Abrahamson    schedule 29.01.2014

Очереди

Использование очередей означает разделение программы на несколько процессов. Например, один процесс, который получает только электронные письма и помещает их в очередь «обработки». Другой процесс извлекает из очереди «обработки» и каким-то образом преобразует сообщение, помещая его в «исходящую» очередь. Это позволяет легко заменять одни детали, не касаясь других. Вы даже можете рассмотреть возможность обработки на другом языке, если производительность плохая. Если вы пишете 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? && mod5.zero?) else if .... но это будет не так очевидно и (что более важно) сложно добавить дополнительные правила.

Для Ruby взгляните на https://github.com/k-tsj/pattern-match хотя я не использовал такие библиотеки в ruby.

ОБНОВЛЕНИЕ:

В своем выступлении Рич упомянул, что систему, подобную прологу, можно использовать для замены условий правилами. core.match не так мощен, как пролог, но он может дать вам представление о том, как можно упростить условия.

person edbond    schedule 29.01.2014
comment
В видео Рич явно сравнивает правила и сопоставление с образцом и находит сопоставление с образцом более сложным, чем правила. Я думаю, что реальным примером Clojure, скорее всего, будет Fogus' Zender. - person J. Abrahamson; 29.01.2014
comment
@J.Abrahamson из этого слайда slideshare.net/slideshow/embed_code/ кажется, что сопоставление с образцом является сложным, и полиморфизм необходим для упрощения. Правила заменяют условия, как я описал. На следующих слайдах написано, что Правила можно получить через Библиотеки, Пролог. Хотя core.match не такой мощный, как Prolog, он упрощает сложные условия. - person edbond; 29.01.2014
comment
@ J.Abrahamson Имеет ли Рич полномочия над тем, что добавляется в основные библиотеки clojure? Если да, то знаете ли вы, почему он позволил им поставить библиотеку сопоставления с образцом? Я действительно видел видео, и я думаю, что точка зрения, которую он делает на сопоставлении с образцом, может быть единственной вещью, которую он говорит, что я не согласен / не понимаю его аргумент. - person Sean Geoffrey Pietz; 31.01.2014
comment
Кроме того, мне интересно, как Рич Хики реализовал бы решение для FizzBuzz. Предпочел бы он условное выражение? - person Sean Geoffrey Pietz; 31.01.2014
comment
Я предполагаю, что да, и я также думаю, что сопоставление с образцом иногда того стоит. Я лично просто думаю, что декомплекция — это не последнее слово в простоте, и сопоставление с образцом играет свою роль. - person J. Abrahamson; 31.01.2014
comment
Кстати, я думаю, что FizzBuzz тоже легко сделать на основе правил. Я думаю, что это отличный пример того, как правила являются хорошим способом. Это приводит к тривиально расширяемому FizzBuzz. - person J. Abrahamson; 31.01.2014