Syntax-Sonntag! Heute:
Man kann in Ruby sehr einfach die vordefinierten Operatoren überschreiben, und zwar so:
class String def + other #-> warning: method redefined; discarding old + self.to_i + other.to_i end end p '5' + '7' #-> 12
Was aber, wenn uns die vordefinierten Operatoren nicht reichen? Würden wir nicht gerne auch so etwas schreiben:
# Da geht sogar der Highlighter kaputt :/ 6 over 49 # Lotto! 8! / 5! # Fakultät [3,4,5] x [1,2,0] # Kreuzprodukt pattern = !/[A-Z]/ && (/^/ + MY_OTHER_PATTERN) # Regexp-Logik
Hier bleibt Ruby stur: Die Syntax, so dynamisch sie auch scheint, ist fest. Wir müssen mit dem auskommen, was der Parser ohne Ausführung des Codes versteht.
Als nicht ganz ernst gemeinten Vorschlag zur Definition beliebiger Infix-Operatoren stelle ich heute die Smiley-Methode vor. Ziel ist die Auswertung solcher Ausdrücke:
a ^:mod^ b # ^ wird extrem selten verwendet
Sieht witzig aus, ist aber nur eine Kurzform für:
a.^(:mod).^(b)
Die Aufrufe sollen nun umgeletet werden auf:
a.mod(b)
Dafür definieren wir Object#^ so, dass ein Proxy generiert wird, der sich einfach das erste Objekt (a im Beispiel) und das Symbol (:mod) merkt.
class Object # Umleitung von ^ auf Proxy def ^ method Proxy.new self, method end end # Proxy mit zwei Attributen # (Zu dieser Konstruktion siehe Link am Ende) class Proxy < Struct.new :object, :method # ruft man proxy.^ auf, wird der eigentliche Aufruf erzeugt def ^ other object.send method, other end end
Was passiert nun mit a ^:mod^ b? Schrittweise.
a ^:mod^ b # für Ruby: a.^(:mod).^(b) # 1. Schritt pr = a.^( :mod ) #-> Proxy.new a, :mod # 2. Schritt pr.^( b ) #-> Proxy bekommt zweiten Operanden # 3. Schritt, in Proxy#^ a.send(:mod, b) #-> a.mod b wird aufgerufen
Wir müssen also noch Fixnum#mod definieren. Hier steht uns noch im Wege, dass die Klasse Fixnum die Methode ^ bereits definiert. Wir müssen sie vorher löschen.
class Fixnum remove_method :^ def mod base self % base end end
Jetzt geht es:
a = 4 b = 2 p a ^:mod^ b #-> 0
Im Prinzip haben wir nicht mehr getan, als eine neue Variante von send zu implementieren. Wir können jetzt jeden Methodenaufruf umleiten:
p Array ^:new^ 3 #-> [nil, nil, nil] self ^:puts^ 'Hello, World!' #-> Hello, World!
Noch ein kleiner Ausflug zur Begründung.
Es gibt eine Fülle überschreibbarer Operatoren (auch Methoden-Operatoren, siehe Pickaxe 2, Tabelle 22.4):
# Unär (ein Operand), Präfix + - # Überschreiben mittels +@ und -@ ~ # Komplement `` # Halte ich auch für einen Operator # Binär, Infix + - * / ** % # Mathematik & | ^ # Logik: und, oder, nicht, xor << >> # append, shift =~ # Mustervergleich == === <=> # Vergleichslogik < <= > >= # Abgeleitete Vergleiche # Binär, "Umfix" (?) [] []= # Elementzugriff (fetch/store)
Nicht überschreiben kann man dagegen alle Operatoren, die den Programmablauf steuern oder die beim Lesen umgewandelt werden:
# Binär != !~ # werden intern umgewandelt && || # steuern die Auswertung .. ... # Ranges # Zuweisung = # Abkürzungen, werden umgewandelt += -= *= /= **= %= &= |= ^= <<= >>= &&= ||= # Ternär ? : # if-then-else # ...und alle Operatoren-Schlüsselwörter
Die meisten Methoden-Operatoren sind für solche Spielereien tabu, weil sie sehr häufig verwendet werden. Man stelle sich vor:
# + und - vertauschen class Fixnum alias plus + alias minus - alias + minus alias - plus end p 4 + 2 #-> 2
Ein Absturz des Interpreters ist vorherzusehen.
Wenn man solche Überraschungen vermeiden will, muss man sich einschränken auf:
Wenn es um die Erweiterung eigener Klassen geht, besteht keine Gefahr.
Wenn man einer vorhandenen Klasse eine Methode hinzufügen will, die sie bisher nicht hatte (Facets definert zB Array#/), dann sollte das auch klappen.
Erwähnenswert sind außerdem noch die Ruby-typischen Kurzmethoden: p und y dürften ja bekannt sein.
Mehr davon!
# Deine Ruby-Version (a=%q:ruby:)^:`^(a^:<<^' -v')^:[]^/\\d\\S+/^:display^$>
Japanische Smileys, wer sie noch nicht kennt o_O' Originalthread auf rubyforen.de class Foo < Struct.new :bar - erklärt Funny Symbols in Code auf rubygarden.org
Kommentar schreiben
Kommentare
Einfach super! Mit den Backticks gibt es aber leider Probleme - oder mir fehlt noch eine Idee. Ein Backtick ist zwar eine Methode in Object, aber irgendwie nichts Infix-artiges. Ich hab mit folgendem Grundschema mal rumgespielt.
class Object alias xeq ` def `(cmd, p2) self.method(cmd.to_sym).call(p2) end end p [1,2,3].`('concat', [4,5,6]) # => [1, 2, 3, 4, 5, 6] p [1,2,3].`(:concat, [4,5,6]) # => [1, 2, 3, 4, 5, 6] p "Hurra! ".`(:*, 3) # => "Hurra! Hurra! Hurra! " p "Hurra! ".`('*', 3) # => "Hurra! Hurra! Hurra! " # Leider geht nicht die Wunschform # [1,2,3] `concat` [4,5,6]Hier ist jetzt nur drin was geht. Wenn ich dagegen auch nur versuche den zweiten Parameter bei der `...`-Form unterzubringen, ernte ich bei jedem Versuch nur ein höhnisches "Syntax Error" ))-: Selbst wenn man das in den Griff bekommt, müsste `...` aber noch ein Infix-Operator sein, weil sonst "[1,2,3] `concat` [4,5,6]" syntaktisch falsch wäre, ausser man bekommt es gebacken, dass "[1,2,3]" eine Methode wird ... hmmm - mal überlegen..Nicht dass es schön ist oder genau dem Wunsch entspricht oder angewandt werden sollte - es kommt auch 'eval' drin vor...
class Object @@infixops = [] alias xeq ` def addinfix(operator) @@infixops << operator end def `(expression) @@infixops.each{|op|break if expression.match(/^(.*?) (#{op}) (.*)$/)} raise "unknown infix operator in expression: #{expression}" if $2 == nil eval($1).method($2.to_sym).call(eval($3)) end end addinfix("concat") p `[1,2,3] concat [4,5,6]` # => [1, 2, 3, 4, 5, 6]Irgendwie wird der Code nicht korrekt angenommen. Ich pack ihn noch mal ins Forum, Ihr könnt ihn ja dann hier reinkopieren.
da siehst du, warum wir WordPress ersetzen wollen.
=~ zu den definierbaren methoden hinzugefügt.
[...] Der letzte Syntax-Sonntag beschäftigte sich ebenfalls mit dem Thema Operatoren. [...]