ruby-mine

exploring the mine

Symbol#to_proc - Ruby funktional

von murphy am 07.03.2006 (11 Uhr)

Der Hack

Es gibt einen absolut wundervollen kleinen Hack für Ruby, der vielseitig einsetzbar ist:

class Symbol
  def to_proc
    proc { |obj, *args| obj.send(self, *args) }
  end
end

Nun kann man faszinierende Dinge tun, indem man

bla { |manamana| manamana.meth }

abkürzt mit bla(&:meth):

# map:
'meine gitarre ist kaputt'.split.map(&:capitalize).join(' ')
#-> "Meine Gitarre Ist Kaputt"
# Sortieren nach Größe:
puts %w'Yodas Grammatik ist ziemlich schlecht'.
  sort_by(&:size).reverse.join(' ')
#-> Grammatik ziemlich schlecht Yodas ist
# Alle Administratoren finden:
@admins = User.find(:all).select(&:admin?)
# fold und Summations-Funktion:
module Enumerable
  def fold msg
    inject(&msg)
  end
end
Array(1..100).fold(:+)  #-> 5050 (= 1 + 2 + ... + 100)

Das letzte ähnelt der foldl1-Funktion aus Haskell. Gefunden habe ich das in den Facets und bei Dave Thomas, aber der Hack ist mindestens seit Anfang 2004 auf Ruby-Talk im Umlauf, also vermutlich nicht Daves Erfindung.

Am einfachsten kommt man an die tolle Methode, indem man den Code kopiert oder sich facets oder extensions via Rubygems installiert:

require 'facet/symbol/to_proc'
# oder
require 'extensions/symbol'

Man muss an die Klammern denken, damit Ruby den Operator richtig erkennt. Es gibt auch noch ein paar Schwierigkeiten, nicht alle Methodenaufrufe lassen sich "symboltoprocen".
Aber man wird demnächst sicher häufiger auf diese Konstruktion stoßen, denn sie ist in Rails 1.1 bereits in ActiveSupport eingebaut.

Erklärung

In Ruby hat man oft mit kurzen Blöcken zu tun, die nicht viel mehr tun, als eine Methode ihres Arguments aufzurufen. Zum Beispiel haben wir eine Datei, in der Hunderte von Zahlen stehen, die wir in einem Array speichern möchten:

numbers = File.read('primes.txt').scan(/d+/)
numbers.map! { |number| number.to_i }

Formulieren wir das ganze um: Eine Ruby-Methode aufzurufen bedeutet, eine Botschaft an ein Objekt zu senden; in Ruby werden diese Botschaften durch Symbole dargestellt. Die Methode send(self) tut genau das:

numbers.map! { |number| number.send(:to_i) }

Das langweilige Muster { |x| x.send(:msg) } ist nur noch von dem verwendeten Symbol abhängig. Machen wir also eine Methode der Klasse Symbol daraus:

class Symbol
  def to_proc
    proc { |x| x.send(self) }
  end
end

:to_i.to_proc liefert also nun ein Proc-Objekt, das wir mithilfe des &-Operators als Block-Argument an map! übergeben können:

numbers.map!(&:to_i.to_proc)

Sieht hässlicher aus als vorher. Glücklicherweise ruft Ruby selber to_proc auf, wenn man ein &-Argument übergibt:

numbers.map!(&:to_i)

Jetzt erlauben wir nur noch zusätzliche Argumente (mit *args) und fertig ist die Methode!

class Symbol
  def to_proc
    proc { |x, *args| x.send(self, *args) }
  end
end


Kommentar schreiben

Name (notwendig)

Mail (wird nicht veröffentlicht)

Webseite


Kommentare

  1. Murphy schrieb am 07.03.2006 (11 Uhr)

    Juchu! Mein erster Blog-Eintrag. Das Highlighting ist übrigends mit CodeRay realisiert (gem install coderay). Das Script zum Umwandeln findet ihr unter http://code.rubychan.de/highlight-wordpress.rb.