2012-11-29 2 views
6

Gibt es eine Möglichkeit, die strenge Arity-Erzwingung eines Proc instanziiert zu aktivieren, der Proc.new oder Kernel.proc instanziiert, so dass es sich wie ein Proc verhält, der mit lambda instanziiert wird?Kann ich Arity für einen Block erzwingen, der an eine Methode übergeben wurde?

Meine initialize Methode nimmt einen Block &action und weist es einer Instanzvariablen zu. Ich möchte action streng Arity erzwingen, also, wenn ich später Argumente darauf anwende, löst es eine ArgumentError, dass ich retten und eine sinnvollere Ausnahme auslösen kann. Grundsätzlich gilt:

class Command 
    attr_reader :name, :action 

    def initialize(name, &action) 
    @name = name 
    @action = action 
    end 

    def perform(*args) 
    begin 
     action.call(*args) 
    rescue ArgumentError 
     raise(WrongArity.new(args.size)) 
    end 
    end 
end 

class WrongArity < StandardError; end 

Leider hat action nicht arity standardmäßig erzwingen:

c = Command.new('second_argument') { |_, y| y } 
c.perform(1) # => nil 

action.to_proc nicht funktioniert, auch nicht lambda(&action).

Irgendwelche anderen Ideen? Oder bessere Ansätze für das Problem?

Danke!

+0

Sie brauchen nicht auf alle '& action' zu verwenden. Löschen Sie einfach den Aktionsparameter und ersetzen Sie in der Zeile, in der Sie die Instanzvariable setzen, "action" durch "lambda". Ich habe unten ein Codebeispiel gepostet. – wrhall

Antwort

8

Ihre @action wird eine Proc Instanz und Proc s haben eine arity Methode sein, so können Sie prüfen, wie viele Argumente der Block haben soll:

def perform(*args) 
    if args.size != @action.arity 
    raise WrongArity.new(args.size) 
    end 
    @action.call(*args) 
end 

, die Pflege von splatless Blöcke wie { |a| ... } nehmen und { |a,b| ... } aber die Dinge sind ein wenig komplizierter mit splats. Wenn Sie einen Block wie { |*a| ... } haben, dann wird @action.arity -1 sein und { |a,*b| ... } wird Ihnen eine arity von -2 geben. Ein Block mit arity -1 kann eine beliebige Anzahl von Argumenten annehmen (einschließlich keiner), ein Block mit arity -2 benötigt mindestens ein Argument, kann aber mehr als das nehmen, und so weiter. Eine einfache Modifikation von splatless Test sollte kümmern sich um die splatful Blöcke:

def perform(*args) 
    if @action.arity >= 0 && args.size != @action.arity 
    raise WrongArity.new(args.size) 
    elsif @action.arity < 0 && args.size < -(@action.arity + 1) 
    raise WrongArity.new(args.size) 
    end 
    @action.call(*args) 
end 
+0

Sie haben Recht, das löst das Problem. Aber ich werde warten und sehen, ob es eine weniger wortreiche Art gibt, es zu tun. Das Verhalten, nach dem ich suche, ist identisch mit Lambda Procs, also möchte ich es nicht neu implementieren, wenn ich es nicht muss (gewährt, meine Command-Klasse führt Proc schon wieder ein, aber es wird unweigerlich mehr Domain- spezifisches Verhalten) – rickyrickyrice

+0

@rickyrickyrice: Ja, es scheint ein wenig klobig, aber ich kann mir nichts besseres vorstellen. OTOH, es ist spät hier, also könnte mir etwas offensichtlich fehlen. –

+0

Es gibt keinen anderen Weg als lambdas. So ist Ruby entworfen: proc hat keine Arity-Checks. Ab 1.9.2 erzwingt Ruby die Prozessvalidierung. – Sigurd

-1

Ihre Lösung:

class Command 
    attr_reader :name, :action 

    def initialize(name) # The block argument is removed 
    @name = name 
    @action = lambda # We replace `action` with just `lambda` 
    end 

    def perform(*args) 
    begin 
     action.call(*args) 
    rescue ArgumentError 
     raise(WrongArity.new(args.size)) 
    end 
    end 
end 

class WrongArity < StandardError; end 

Einige Referenzen: „Wenn Proc.new aus innerhalb einer Methode ohne Argumente aufgerufen wird, es wird einen neuen Proc zurückgeben, der den Block enthält, der seiner umgebenden Methode gegeben wurde. " - http://mudge.name/2011/01/26/passing-blocks-in-ruby-without-block.html

Es stellt sich heraus, dass Lambda in der gleichen Weise funktioniert.

+0

Faszinierend, aber ich glaube nicht, dass es funktioniert: (irb): 7: Warnung: versucht, Proc-Objekt ohne Block zu erstellen – rickyrickyrice

+1

Eigentlich denke ich, es funktioniert trotz der Warnungen, aber die Tatsache, dass es diese Warnungen gibt, deutet darauf hin keine ideale Lösung! –

+0

Also, wenn ich nur das Codebeispiel kopierte/einfügte (und die Prüfung in Ihrem OP durchführte), bekam ich keine Warnungen. Aber wenn ich es individuell führe, tue ich es. Außerdem scheint es nur Blöcke in Lambda zu konvertieren, wenn es einen Prozess passiert, scheint es es nicht zu konvertieren. Ich denke, die oben genannten zwei Lösungen sind fast definitiv koscherer, aber ich sehe kein Problem damit, wenn Sie nur Blöcke in Lambda konvertieren und immer eine Blockade garantiert sind. – wrhall

1

Nach this answer ist die einzige Möglichkeit, ein Proc in ein Lambda zu konvertieren, define_method und Freunde. Vom docs:

define_method definiert immer ein Verfahren ohne die Tricks [das heißt ein Lambda-Stil Proc], auch wenn ein nicht-Lambda-Proc-Objekt gegeben ist. Dies ist die einzige Ausnahme, bei der die Tricks nicht erhalten bleiben.

Spezifisch, ebenso wie tatsächlich eine Methode zu definieren, gibt define_method(:method_name, &block) ein Lambda zurück. Um dies zu verwenden, ohne unnötig viele Methoden auf einem schlechten Objekt zu definieren, könnten Sie define_singleton_method für ein temporäres Objekt verwenden.

So könnte man so etwas tun:

def initialize(name, &action) 
    @name = name 
    @action = to_lambda(&action) 
end 

def perform(*args) 
    action.call(*args) 
    # Could rescue ArgumentError and re-raise a WrongArity, but why bother? 
    # The default is "ArgumentError: wrong number of arguments (0 for 1)", 
    # doesn't that say it all? 
end 

private 

def to_lambda(&proc) 
    Object.new.define_singleton_method(:_, &proc) 
end 
Verwandte Themen