Übergeben einer Methode als Parameter in Ruby

117

Ich versuche ein bisschen mit Ruby herumzuspielen. Daher versuche ich, die Algorithmen (in Python angegeben) aus dem Buch "Programming Collective Intelligence" Ruby zu implementieren.

In Kapitel 8 übergibt der Autor eine Methode a als Parameter. Dies scheint in Python zu funktionieren, aber nicht in Ruby.

Ich habe hier die Methode

def gaussian(dist, sigma=10.0)
  foo
end

und möchte dies mit einer anderen Methode aufrufen

def weightedknn(data, vec1, k = 5, weightf = gaussian)
  foo
  weight = weightf(dist)
  foo
end

Ich habe nur einen Fehler

ArgumentError: wrong number of arguments (0 for 1)
Christian Stade-Schuldt
quelle

Antworten:

100

Sie möchten ein Proc-Objekt:

gaussian = Proc.new do |dist, *args|
  sigma = args.first || 10.0
  ...
end

def weightedknn(data, vec1, k = 5, weightf = gaussian)
  ...
  weight = weightf.call(dist)
  ...
end

Beachten Sie nur, dass Sie in einer solchen Blockdeklaration kein Standardargument festlegen können. Sie müssen also einen Splat verwenden und den Standard im Proc-Code selbst einrichten.


Abhängig von Ihrem Umfang ist es möglicherweise einfacher, stattdessen einen Methodennamen zu übergeben.

def weightedknn(data, vec1, k = 5, weightf = :gaussian)
  ...
  weight = self.send(weightf)
  ...
end

In diesem Fall rufen Sie nur eine Methode auf, die für ein Objekt definiert ist, anstatt einen vollständigen Codeabschnitt zu übergeben. Je nachdem , wie Sie diese Struktur , die Sie brauchen ersetzen kann self.sendmitobject_that_has_the_these_math_methods.send


Zu guter Letzt können Sie einen Block an die Methode hängen.

def weightedknn(data, vec1, k = 5)
  ...
  weight = 
    if block_given?
      yield(dist)
    else
      gaussian.call(dist)
    end
  end
  ...
end

weightedknn(foo, bar) do |dist|
  # square the dist
  dist * dist
end

Es hört sich jedoch so an, als ob Sie hier mehr wiederverwendbare Codestücke wünschen.

Alex Wayne
quelle
1
Ich denke, dass die zweite Option die beste Option ist (dh mit Object.send ()). Der Nachteil ist, dass Sie für alles eine Klasse verwenden müssen (wie Sie es in OO sowieso tun sollten :)). Es ist trockener als das ständige Übergeben eines Blocks (Proc), und Sie können sogar Argumente über die Wrapper-Methode übergeben.
Jimmy Stenke
4
Als Ergänzung, wenn Sie foo.bar(a,b)mit Senden tun möchten , ist es foo.send(:bar, a, b). Der splat (*) -Operator ermöglicht es Ihnen, foo.send(:bar, *[a,b])wenn Sie feststellen, dass Sie ein beliebig langes Array von Argumenten haben möchten - vorausgesetzt, die
Balkenmethode
99

Die Kommentare zu Blöcken und Prozessen sind insofern korrekt, als sie in Ruby üblicher sind. Sie können jedoch eine Methode übergeben, wenn Sie möchten. Sie rufen methodauf, um die Methode abzurufen und .callaufzurufen:

def weightedknn( data, vec1, k = 5, weightf = method(:gaussian) )
  ...
  weight = weightf.call( dist )
  ...
end
Daniel Lucraft
quelle
3
Das ist interessant. Es ist erwähnenswert, dass Sie method( :<name> )nur einmal aufrufen, wenn Sie Ihren Methodennamen in ein aufrufbares Symbol konvertieren. Sie können dieses Ergebnis in einer Variablen oder einem Parameter speichern und es von nun an wie jede andere Variable an
1
Oder anstatt die Methodensyntax in der Argumentliste zu verwenden, können Sie sie beim Aufrufen der Methode wie folgt verwenden: weightedknn (data, vec1, k, method (: gaussian))
Yahya
1
Diese Methode ist besser als mit einem Proc oder Block herumzuspielen, da Sie nicht mit Parametern umgehen müssen - sie funktioniert nur mit dem, was die Methode will.
Danuker
3
Wenn Sie zum Abschluss eine an einer anderen Stelle definierte Methode übergeben möchten, tun Sie dies SomewhereElse.method(:method_name). Das ist ziemlich toll!
medik
Dies mag eine eigene Frage sein, aber wie kann ich feststellen, ob ein Symbol auf eine Funktion oder etwas anderes verweist? Ich habe es versucht, :func.classaber das ist nur einsymbol
quietContest
46

Sie können eine Methode als Parameter mit method(:function)way übergeben. Unten ist ein sehr einfaches Beispiel:

def double (a)
  return a * 2 
Ende
=> Null

def method_with_function_as_param (Rückruf, Nummer) 
  callback.call (Nummer) 
Ende 
=> Null

method_with_function_as_param (method (: double), 10) 
=> 20
Patrick Wong
quelle
7
Ich hatte ein Problem mit einer Methode mit einem komplizierteren Umfang und fand schließlich heraus, wie es geht. Ich hoffe, dies kann jemandem helfen: Wenn sich Ihre Methode beispielsweise in einer anderen Klasse befindet, sollten Sie die letzte Codezeile wie method_with_function_as_param(Class.method(:method_name),...)und nichtmethod(:Class.method_name)
V
Dank Ihrer Antwort habe ich die aufgerufene Methode entdeckt method. Ich habe meinen Tag gemacht, aber ich denke, deshalb bevorzuge ich funktionale Sprachen. Ich muss keine solche Akrobatik machen, um das zu bekommen, was Sie wollen. Wie auch immer, ich grabe Rubin
Ludovic Kuty
25

Der normale Ruby-Weg, dies zu tun, ist die Verwendung eines Blocks.

Es wäre also so etwas wie:

def weightedknn( data, vec1, k = 5 )
  foo
  weight = yield( dist )
  foo
end

Und verwendet wie:

weightenknn( data, vec1 ) { |dist| gaussian( dist ) }

Dieses Muster wird in Ruby häufig verwendet.

Futter
quelle
1

Sie müssen die Methode "call" des Funktionsobjekts aufrufen:

weight = weightf.call( dist )

EDIT: Wie in den Kommentaren erläutert, ist dieser Ansatz falsch. Es würde funktionieren, wenn Sie Procs anstelle normaler Funktionen verwenden.

Tiago
quelle
1
Wenn er dies weightf = gaussianin der Arg-Liste tut, versucht er tatsächlich gaussian, das Ergebnis als Standardwert für weightf auszuführen und zuzuweisen. Für den Anruf sind keine Argumente und Abstürze erforderlich. Weightf ist also noch nicht einmal ein Proc-Objekt mit einer Aufrufmethode.
Alex Wayne
1
Dies (dh es falsch zu machen und der Kommentar zu erklären, warum) erlaubte mir tatsächlich, die akzeptierte Antwort vollständig zu verstehen, also danke! +1
rmcsharry
1

Ich würde empfehlen, kaufmännisches Und zu verwenden, um Zugriff auf benannte Blöcke innerhalb einer Funktion zu erhalten. Wenn Sie den Empfehlungen in diesem Artikel folgen, können Sie so etwas schreiben (dies ist ein echter Ausschnitt aus meinem Arbeitsprogramm):

  # Returns a valid hash for html form select element, combined of all entities
  # for the given +model+, where only id and name attributes are taken as
  # values and keys correspondingly. Provide block returning boolean if you
  # need to select only specific entities.
  #
  # * *Args*    :
  #   - +model+ -> ORM interface for specific entities'
  #   - +&cond+ -> block {|x| boolean}, filtering entities upon iterations
  # * *Returns* :
  #   - hash of {entity.id => entity.name}
  #
  def make_select_list( model, &cond )
    cond ||= proc { true } # cond defaults to proc { true }
    # Entities filtered by cond, followed by filtration by (id, name)
    model.all.map do |x|
      cond.( x ) ? { x.id => x.name } : {}
    end.reduce Hash.new do |memo, e| memo.merge( e ) end
  end

Danach können Sie diese Funktion folgendermaßen aufrufen:

@contests = make_select_list Contest do |contest|
  logged_admin? or contest.organizer == @current_user
end

Wenn Sie Ihre Auswahl nicht filtern müssen, lassen Sie den Block einfach weg:

@categories = make_select_list( Category ) # selects all categories

Soviel zur Kraft der Ruby-Blöcke.

Vitrums
quelle
-5

Sie können auch "eval" verwenden, die Methode als Zeichenfolgenargument übergeben und sie dann einfach in der anderen Methode auswerten.

Konstantin
quelle
1
Das ist wirklich eine schlechte Übung, mach es niemals!
Entwickler
@ Entwickler warum wird dies als schlechte Praxis angesehen?
Jlesse
Unter Leistungsgründen evalkann der Code beliebigen Code ausführen, sodass er extrem anfällig für verschiedene Angriffe ist.
Entwickler