Rubyでパラメーターとしてメソッドを渡す


117

Rubyを少しいじってみます。そのため、私は "Programming Collective Intelligence" Rubyのアルゴリズム(Pythonで提供)を実装しようとしています。

第8章では、作成者はメソッドaをパラメーターとして渡します。これはPythonでは機能するようですが、Rubyでは機能しません。

ここにメソッドがあります

def gaussian(dist, sigma=10.0)
  foo
end

これを別のメソッドで呼び出したい

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

私が得たすべてはエラーです

ArgumentError: wrong number of arguments (0 for 1)

回答:


100

あなたはprocオブジェクトが欲しい:

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

このようなブロック宣言では、デフォルトの引数を設定できないことに注意してください。したがって、スプラットを使用し、procコード自体にデフォルトを設定する必要があります。


または、これらすべてのスコープによっては、代わりにメソッド名を渡す方が簡単な場合があります。

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

この場合、コードの完全なチャンクを渡すのではなく、オブジェクトで定義されているメソッドを呼び出すだけです。これをどのように構成するかに応じて、次のものself.sendと置き換える必要がありますobject_that_has_the_these_math_methods.send


最後に重要なことですが、メソッドからブロックを掛けることができます。

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

しかし、ここでは再利用可能なコードのチャンクがもっと必要なようです。


1
私は2番目のオプションが最良のオプションだと思います(つまり、Object.send()を使用します)。欠点は、そのすべてにクラスを使用する必要があることです(これはOOで行う方法です:))。常にブロック(Proc)を渡すよりも乾燥しており、ラッパーメソッドを介して引数を渡すこともできます。
ジミー・ステンケ、2009

4
おまけにfoo.bar(a,b)sendでやりたいならですfoo.send(:bar, a, b)。splat(*)演算子を使用するとfoo.send(:bar, *[a,b])、任意の長さの引数の配列が必要な場合に実行できます-barメソッドが引数を吸収できると仮定
xxjjnn

99

Rubyでより一般的であるという点で、ブロックとプロシージャに関するコメントは正しいです。ただし、必要に応じてメソッドを渡すことができます。methodメソッドを取得して呼び出すには、次の.callように呼び出します。

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

3
これは面白い。method( :<name> )メソッド名を呼び出し可能なシンボルに変換するときに一度だけ呼び出すことは注目に値します。その結果を変数またはパラメーターに格納し、それを他の変数と同様に子関数に渡し続けることができます...

1
または、引数リストでメソッド構文を使用する代わりに、メソッドを呼び出すときに次のように使用できます:weightedknn(data、vec1、k、method(:gaussian))
Yahya

1
パラメータを処理する必要がないので、このメソッドはprocやブロックをいじくり回すよりも優れています。メソッドが望むもので機能するだけです。
danuker

3
完了のために、他の場所で定義されたメソッドを渡したい場合は、を実行してくださいSomewhereElse.method(:method_name)。それはいいね!
メディック2016年

これは独自の質問かもしれませんが、シンボルが関数または何か他のものを参照しているかどうかをどのように判断できますか?試してみました:func.classが、それは単なる問題ですsymbol
quietContest 2017年

46

あなたは方法としてパラメータとしてメソッドを渡すことができますmethod(:function)。以下は非常に単純な例です:

def double(a)
  * 2を返す 
終わり
=> nil

def method_with_function_as_param(callback、number) 
  callback.call(数値) 
終わり 
=> nil

method_with_function_as_param(method(:double)、10) 
=> 20

7
私は、より複雑なスコープとメソッドの問題に直面し、最終的に行う方法を考え出し、この缶のヘルプ誰かを願って:あなたのメソッドが別のクラスでは、たとえばある場合は、次のようなコードの最後の行を呼び出す必要がmethod_with_function_as_param(Class.method(:method_name),...)ありませんmethod(:Class.method_name)
Vを。Déhaye2017

あなたの答えのおかげで、私はと呼ばれる方法を発見しましたmethod。私の日を作ったが、それが私が関数型言語を好む理由だと思います、あなたが望むものを得るためにそのような曲芸をする必要はありません。とにかく、ルビーを掘る
Ludovic Kuty

25

これを行う通常のRubyの方法は、ブロックを使用することです。

したがって、次のようになります。

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

そして次のように使用されます:

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

このパターンはRubyで広く使用されています。



1

関数オブジェクトのメソッド「call」を呼び出す必要があります:

weight = weightf.call( dist )

編集:コメントで説明されているように、このアプローチは間違っています。通常の関数の代わりにProcsを使用している場合に機能します。


1
彼がweightf = gaussian引数リストで実行する場合gaussian、実際には実行して、結果をweightfのデフォルト値として割り当てようとしています。呼び出しに必要な引数とクラッシュはありません。そのため、weightfはまだ、callメソッドを備えたprocオブジェクトではありません。
アレックスウェイン

1
これ(つまり、間違っていることと、理由を説明するコメント)により、実際に受け入れられた回答を完全に理解することができました。+1
rmcsharry

1

アンパサンドを使用して、関数内の名前付きブロックにアクセスすることをお勧めします。この記事で示した推奨事項に従って、次のようなものを書くことができます(これは私の作業プログラムからの実際のスクラップです):

  # 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

その後、この関数を次のように呼び出すことができます。

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

選択をフィルタリングする必要がない場合は、ブロックを省略します。

@categories = make_select_list( Category ) # selects all categories

Rubyブロックの能力はこれで終わりです。


-5

また、「eval」を使用して、メソッドを文字列引数として渡し、他のメソッドで単純に評価することもできます。


1
これは本当に悪い習慣です、決してやらないでください!
開発者

@Developerはなぜこれが悪い習慣と見なされているのですか?
jlesse 2018年

パフォーマンス上の理由から、はeval任意のコードを実行できるため、さまざまな攻撃に対して非常に脆弱です。
開発者
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.