2014-01-29 9 views
14

Rich Hickey beschreibt Paradigmen von Clojure und Haskell in seinem Vortrag Simple Made Easy. Als Rubin/Schienen-Programmierer (das ist alles, wirklich ich weiß), ich liebte seine Ideen, aber nicht verstehen, 2 von ihnen:Warteschlangen statt Methodenverkettung und Regeln statt Bedingungen in Ruby

  • Warteschlangen mit, nicht Methode Chaining
  • Regeln statt Conditionals

mit Warteschlangen statt

Offensichtlich in Rails wir lieben Methode Verkettungs, aber ich wollte verstehen, was eine Warteschlange wie in Ruby so aussehen, würde er es beschrieben (54:54 im Video):

Wenn Sache A Sache B anruft, hast du sie nur vervollständigt. Du hast eine wann und wo Sache. A muss wissen, wo B ist, um B anzurufen. Wenn das passiert, ist es immer dann, wenn A es tut. Halte eine Warteschlange dort drin.

Regeln vs Conditionals

Er spricht mit etwa nicht conditionals oder switch-Anweisungen, sondern Regeln statt (30:00 in Video).

Dies bekomme ich einfach nicht in Bezug auf Ruby. Wie treffe ich Entscheidungen ohne Konditionale?

Dank alle, Justin

+13

Wie passt Haskell oder Clojure dazu? –

+2

@ ThomasM.DuBuisson Hickey's Vortrag basiert auf Paradigmen, die er für Clojure kreiert hat und was er von Haskell mochte. Hoffte auf Einsicht von der Seite der Gemeinschaft, die mit seinen Lehren arbeitet. – nitsujri

+0

Sie implizieren Bedingungen aus Regeln, anstatt Bedingungen direkt zu schreiben. – Vektorweg

Antwort

15

Hallo, Warteschlangen

Die Idee dabei ist, dass statt einen Wert direkt von einem Objekt zu einem anderen vorbei, wir sie durch Kleben eine Warteschlange zwischen ihnen abkoppeln können.

Nehmen wir an, wir modellieren einen Bauern, der die Eier von einem Huhn sammelt. Das Huhn produziert Eier, der Bauer sammelt sie. Die Schicht eines Bauern ist beendet, wenn sie fünf Eier gesammelt haben.Normalerweise schreiben wir vielleicht so etwas:

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 

Also, der Bauer wartet am Huhn und holt die Eier, wie sie kommen. Toll, Problem gelöst, gehe zum Kühlschrank und hol dir ein Bier. Aber was wäre, wenn wir, sagen wir, ein zweites Huhn kaufen würden, um unsere Eierproduktion zu verdoppeln? Oder, wenn wir die Geschicklichkeit unseres Landwirts testen wollten, indem wir Eier aus einem Karton holen?

Da wir den Landwirt so programmiert haben, dass er ein Huhn benötigt, haben wir die Flexibilität verloren, die wir brauchen, um diese Art von Entscheidungen zu treffen. Wenn wir sie entkoppeln können, haben wir viel mehr Freiheit.

Also, lasst uns eine Warteschlange zwischen ihnen stecken. Das Huhn legt Eier oben auf eine Rutsche; Der Bauer wird Eier vom Boden der Rutsche sammeln. Keine Partei ist direkt auf die andere angewiesen. In Code, dass könnte wie folgt aussehen:

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 

Abgesehen davon, dass jetzt können wir leicht ein zweites Huhn hinzufügen. Dies ist der Stoff aus dem Träume gemacht sind:

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

Man könnte sich vorstellen, wie wir auch könnte, sagen wir, eine Rutsche mit einem Bündel von Eiern den Bauern zu testen laden. Keine Notwendigkeit, ein Huhn zu verspotten, prep wir nur eine Warteschlange und geben es in.

Good-bye, Conditionals

Meine Antwort auf diese Frage ist vielleicht ein wenig mehr umstritten, aber viel kürzer. Man könnte sich multimethods in Ruby ansehen, aber der Kern der Idee ist es, auf geschlossene, hartkodierte logische Pfade zugunsten von offenen zu verzichten, und in der Tat erreicht polynomischer Polymorphismus genau dies.

Immer wenn Sie die Methode eines Objekts aufrufen, anstatt seinen Typ einzuschalten, nutzen Sie das typbasierte Regelsystem von Ruby, anstatt einen logischen Pfad fest zu codieren. Offensichtlich ist dies:

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 

ist weniger offen als dies:

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 

Hier Polymorphismus uns sind gegeben, ein Mittel zu beschreiben, wie das System mit unterschiedlichen Daten verhält, die uns neues Verhalten für neue Daten einzuführen erlaubt aus einer Laune heraus. Also, gute Arbeit, du vermeidest (hoffentlich) jeden Tag!

+0

Das Beispiel "Hello, Queues" ist eine sehr klare Erklärung des Konzepts "Entkopplung über Warteschlangen", aber auf Kosten eines anderen Aspekts: Wertorientierung. Eine gemeinsame Referenz, auf die durch Referenz zugegriffen werden kann, erbt die Concurrency-Probleme, vor denen im Gespräch gewarnt wird. Ein wertorientierter Ansatz würde Funktionen aufrufen, die je nach Funktion eine Warteschlange aufnehmen und eine neue Warteschlange mit mehr oder weniger Eiern zurückgeben. Dieser Ansatz würde es jedoch zu einer seriellen Lösung machen, da q an/mit jedem Objekt, das es betrifft, übergeben werden muss. Ich würde gerne wissen, was das funktionale Programmiermuster dafür ist. –

4

werden von Haskell Keiner dieser beiden Punkte terrifically gut ausgebildet. Ich denke, Haskell führt immer noch zu einem etwas unkompilierten Code, aber nähert sich dem ganzen Problem sowohl mit einer anderen Philosophie als auch mit anderen Tools.

Queues

Grob Hickey darauf hin will, dass, wenn Sie eine Methode für ein Objekt zu schreiben, die

ein anderes Objekt aufruft
class Foo 
    def bar(baz) 
    baz.quux 
    end 
end 

dann haben wir nur den Begriff hartcodiert, dass Was immer in Foo#bar übergeben wird, muss eine quux Methode haben. Es ist eine Komplettierung in seiner Sicht, weil es bedeutet, dass die Implementierung Foo von Natur aus an die Implementierung der Implementierung eines Objekts gebunden ist, das an Foo#bar übergeben wird.

Dies ist weniger ein Problem in Ruby, wo Methodenaufruf eher wie eine dynamisch verteilte Nachricht zwischen Objekten gesendet wird. Es bedeutet einfach, dass ein Objekt, das an Foo#bar übergeben wird, irgendwie verantwortlich antworten muss, wenn die quux Nachricht gegeben wird, nicht viel mehr.

Aber es bedeutet Sequenzialität in der Nachrichtenbehandlung. Wenn Sie stattdessen die Nachricht in einer Warteschlange ablegen, um sie schließlich an das resultierende Objekt zu senden, können Sie problemlos einen Vermittler an dieser Naht platzieren. Vielleicht möchten Sie bar und quux gleichzeitig ausführen.

Mehr als Haskell, diese Idee wird in Erlang zu einem logischen Extrem gebracht und ich empfehle dringend zu lernen, wie Erlang diese Art von Problemen löst.

spawn(fun() -> Baz ! quux) 

Regeln

Hickey betont immer wieder, dass insbesondere hartcodierte Methoden der complect Dinge Verzweigung zu tun. Um zu zeigen, genießt er keine Case-Anweisung oder Mustererkennung. Stattdessen schlägt er Regeln vor, von denen ich annehme, dass er "Produktionsregeln" -Systeme meint. Diese erzeugen Auswahl und Verzweigung, indem sie einem Programmierer erlauben, eine Reihe von Regeln für das "Auslösen" bestimmter Aktionen einzurichten und dann zu warten, bis eingehende Ereignisse ausreichende Regeln erfüllen, um das Auslösen von Aktionen auszulösen. Die bekannteste Implementierung dieser Ideen ist Prolog.

Haskell hat Muster tief in seine Seele gebaut Matching, so ist es schwer zu argumentieren, dass Haskell sofort auf diese Weise decomplects ... aber es ist ein wirklich gutes Beispiel für ein Regelsystem am Leben in Haskell- Typ-Klasse Auflösung .

wohl bekannteste Begriff hierfür ist mtl -Stil typeclasses wo Sie Schreibfunktionen am Ende mit Signaturen wie

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

wo foom so lange in der Art vollständig polymorph ist, wie es bestimmte folgt Zwänge-it muss einen konstanten Kontext haben r, ein veränderbarer Kontext s, die Fähigkeit, IO auszuführen, und die Fähigkeit, Ausnahmen zu werfen und abzufangen.

Die tatsächliche Auflösung, welche Arten alle diese Bedingungen instanziieren, wird durch ein Regelsystem gelöst, das oft (liebevoll oder anderweitig) "type class prolog" genannt wird. In der Tat, es ist ein leistungsfähiges System genug, um ganze Programme innerhalb des Systemtyps zu kodieren.

Es ist wirklich wirklich nett und gibt Haskell eine Art natürlicher Abhängigkeit-Einspritzungsart, wie oben durch das mtl Beispiel beschrieben wird.

Ich denke jedoch, dass die meisten Haskeller nach langem Gebrauch eines solchen Systems verstehen, dass, obwohl Regelsysteme manchmal schlau sind, sie auch leicht außer Kontrolle geraten können. Haskell hat eine Los von sorgfältigen Einschränkungen der Macht der Klasse Prolog Klasse, die sicherstellen, dass es für einen Programmierer einfach ist, vorherzusagen, wie es sich auflösen wird.

Und das ist ein primäres Problem mit Regelsystemen insgesamt: Sie verlieren die explizite Kontrolle darüber, welche Aktionen am Ende feuern ... so wird es schwieriger, Ihre Regeln zu massieren, um das Ergebnis zu erzielen, das Sie erwarten. Ich bin mir nicht wirklich sicher, dass ich Rich hier zustimme, dass Regelsysteme so zur Dekompression führen. Sie können nicht explizit Informationen abzweigen, die an andere Objekte gebunden sind, aber Sie richten viele unscharfe, weitreichende Abhängigkeiten zwischen den Dingen ein.

1

Queues

Verwendung Warteschlangen Split-Programm in mehrere Prozesse bedeutet. Zum Beispiel ein Prozess, der nur E-Mails empfängt und sie in die Warteschlange "Verarbeitung" versetzt. Der andere Prozess zieht aus der "Verarbeitungs" -Warteschlange und wandelt die Nachricht irgendwie in eine "Ausgangs" -Warteschlange um. Dies ermöglicht es, einige Teile leicht zu ersetzen, ohne andere zu berühren. Sie können sogar in anderen Sprachen arbeiten, wenn die Leistung schlecht ist. Wenn Sie e=Email.fetch; Processor.process(e) schreiben, sind Sie Paar alle Prozesse zusammen.

Ein weiterer Vorteil von Warteschlangen ist, dass es viele Produzenten und Konsumenten geben kann. Sie können leicht "Skalierung" Verarbeitung Teil nur durch Hinzufügen von mehr "Verarbeitung" Prozesse (mit Threads, andere Maschinen usw.). Auf der anderen Seite können Sie auch mehr "Email Fetcher" Prozesse starten. Dies ist kompliziert, wenn Sie alle in einem Anruf zusammenfassen.

Es gibt eine einfache Warteschlange in Ruby http://ruby-doc.org/stdlib-1.9.3/libdoc/thread/rdoc/Queue.html und viele andere (rabbitmq, db-Basis usw.)

Regeln

Regeln Code unkompliziert macht. Anstelle von Wenn-Dann-sonst werden Sie ermutigt, Regeln zu erstellen. Werfen Sie einen Blick auf 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))) 

können Sie schreiben, wenn (mod3.zero & & mod5.zero?) Else if .... aber es wird nicht so offensichtlich und (wichtiger) schwer mehr Regeln hinzuzufügen.

Für Ruby werfen Sie einen Blick auf https://github.com/k-tsj/pattern-match, obwohl ich solche Bibliotheken in Ruby nicht verwendet habe.

UPDATE:

In seinem Vortrag kann Reich erwähnt, dass prolog-ähnliches System verwendet wird Bedingungen mit Regeln zu ersetzen. core.match ist nicht so mächtig wie Prolog, aber es kann Ihnen eine Vorstellung davon geben, wie Bedingungen vereinfacht werden können.

+1

Im Video vergleicht Rich explizit Regeln und Mustervergleiche und stellt fest, dass Mustervergleiche komplizierter sind als Regeln. Ich denke, ein echtes Clojure-Beispiel ist eher [Fogus 'Zender] (http://www.youtube.com/watch?v=1E2CoObAaPQ). –

+0

@ J.Abrahamson von dieser Folie http://www.slideshare.net/slideshow/embed_code/15091798?rel=0&startSlide=16 scheint es Mustervergleich ist "Complex" und Polymorphie benötigt, um zu vereinfachen. Regeln ersetzen Bedingungen, wie ich sie beschrieben habe. Auf den nächsten Folien steht, dass "Regeln" über "Bibliotheken, Prolog" abgerufen werden können. Obwohl core.match nicht so mächtig ist wie Prolog, vereinfacht es komplexe Bedingungen. – edbond

+0

@ J.Abrahamson Hat Rich Autorität darüber, was zu den clojure core libs hinzugefügt wird? Wenn ja, wisst ihr, warum er sie die Pattern-Matching-Bibliothek anlegen ließ? Ich habe das Video gesehen und denke, dass der Punkt, den er bei der Mustererkennung macht, die eine Sache ist, die er sagt, dass ich seine Argumentation nicht stimme. –