ruby-mine

exploring the mine

Spass mit HEAD: inspizierbare Blockparameter in 1.9.2

von skade am 31.05.2009 (16 Uhr)

Es ist zwar noch eine ganze Weile bis Weihnachten, aber eins ist sicher: Ruby 1.9.2 kommt. Und mit ihm ein Herzenswunsch Yehuda Katz's, der es wegen später Einreichung nicht mehr in 1.9.1 geschafft hat, aber im trunk vorhanden ist: Proc#parameters. Einen kleinen Spass, den man sich damit leisten kann, findet ihr unter dem Link.

Proc#parameters tut das, was der Name sagt: es gibt einem die Parameter eines Blocks zurück:


a =->(a,b=0){}.parameters #=> [[:req, :a], [:opt, :b]]

Spassig, braucht mans? Aber klar! Wie wärs mit Named Captures bei regulären Ausdrücken?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def named_match(string, regexp, &block)
  match = string.match(regexp)
  
  if match 
    arguments = []
    block.parameters.each do |required, name|
      name = name.to_s #name ist ein symbol, match will strings
      if match.names.include? name
        arguments << match[name]
      elsif required == :req
        raise Exception.new("Required arg missing in regexp: #{name}")
      end
    end
    
    block.(*arguments)
  end
end

named_match "123abc", /(?<number>\d+)(?<string>\w*)/ do |number, string|
  puts number
  puts string
end

named_match "123abc", /(?<number>\d+)(?<string>\w*)/ do |string, number|
  puts number
  puts string
end

Natürlich lässt sich das weiter ausweiten. Wie wärs mit Hash#with_values ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Hash
  def with_values(&block)
    arguments = []
    block.parameters.each do |required, name|
      if !(v = self[name]) && required == :req
        raise Exception.new("Required key missing: #{name}")
      end
      arguments << v
    end
    
    block.(*arguments)
  end
end

{a: "b", c: "d", e: "f"}.with_values do |a,e|
  puts a,e
end

Dummerweise kriegt man default-Parameter nicht mitgeliefert (angeblich schwierig zu implementieren, gerade weil der default code sein kann), sonst hätte ich sofort sowas geschrieben:

1
2
3
xml_document.css do |name="person > name", street="person > street"|
  #schade... *schnief*... geht nicht
end

Nachtrag:

Für Nutzer von Ruby 1.9.1 gibt es das Feature als gem:


gem19 install methopara


Kommentar schreiben

Name (notwendig)

Mail (wird nicht veröffentlicht)

Webseite


Kommentare

  1. WoNáDo schrieb am 02.06.2009 (00 Uhr)

    Nette Neuerung. Mal sehen, was man damit noch alles anstellen kann - Dein Beispiel führt ja schon in eine bestimmte Richtung. Wegen der Match-Geschichte kann man aber schont jetzt was machen, weil es ein "Magisches Variablenverhalten" in Ruby 1.9 gibt... -> /(?.)/=~"XYZ";otto ...liefert "X".

  2. murphy schrieb am 02.06.2009 (01 Uhr)

    Die Highlighting-Schwachstelle für block.(*arguments) wird in der nächsten Version gefixt. Ich bevorzuge aber die [...]-Schreibweise...

  3. Skade schrieb am 02.06.2009 (07 Uhr)

    Das praktische an .() ist, dass es ein alias für #call ist und damit [] nicht verbraucht. Ich verwende das gerne für Objekte, die #call implementieren, aber nicht das #[]-Verhalten von Blocks haben. Allerdings ist es auch der schnelle Weg zu Ruby-1.9 only Code. Aber hey - ich verwend ja auch das picky lamdba.