Ruby'de bir yöntemi parametre olarak geçirmek


117

Ruby ile biraz uğraşmaya çalışıyorum. Bunun için Ruby "Programming Collective Intelligence" kitabındaki algoritmaları (Python'da verilen) uygulamaya çalışıyorum.

Bölüm 8'de yazar bir yöntemi parametre olarak geçirir. Bu Python'da çalışıyor gibi görünüyor ama Ruby'de değil.

Burada yöntemim var

def gaussian(dist, sigma=10.0)
  foo
end

ve bunu başka bir yöntemle çağırmak istiyorum

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

Tek sahip olduğum bir hata

ArgumentError: wrong number of arguments (0 for 1)

Yanıtlar:


100

Bir proc nesnesi istiyorsunuz:

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

Bunun gibi bir blok bildiriminde varsayılan bir argüman ayarlayamayacağınızı unutmayın. Bu yüzden bir uyarı kullanmanız ve proc kodunun kendisinde varsayılanı ayarlamanız gerekir.


Veya, tüm bunların kapsamına bağlı olarak, bunun yerine bir yöntem adı geçirmek daha kolay olabilir.

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

Bu durumda, tam bir kod yığınını iletmek yerine, yalnızca bir nesne üzerinde tanımlanmış bir yöntemi çağırırsınız. Bunu nasıl yapılandırdığınıza bağlı olarak self.send,object_that_has_the_these_math_methods.send


Son fakat en az değil, yönteme bir blok asabilirsiniz.

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

Ancak, burada daha fazla yeniden kullanılabilir kod parçası istiyorsunuz gibi görünüyor.


1
İkinci seçeneğin en iyi seçenek olduğunu düşünüyorum (yani, Object.send () kullanarak), dezavantajı, hepsi için bir sınıf kullanmanız gerekmesidir (zaten OO'da da böyle yapmalısınız :)). Her zaman bir bloğu (Proc) iletmekten daha KURU ve hatta sarmalayıcı yöntemiyle argümanlar iletebilirsiniz.
Jimmy Stenke

4
Ek olarak, foo.bar(a,b)gönder ile yapmak istiyorsanız , öyle foo.send(:bar, a, b). Splat (*) operatörü, foo.send(:bar, *[a,b])keyfi olarak uzun bir argüman dizisine sahip olmak istediğinizi fark etmeniz durumunda yapmanıza olanak sağlar - bar yönteminin onları
emebileceğini

99

Bloklara ve Proc'lara atıfta bulunan yorumlar, Ruby'de daha yaygın oldukları için doğrudur. Ama isterseniz bir yöntemi geçebilirsiniz. Sen buna methodyöntemini almak ve .callaramak için:

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

3
Bu ilginç. method( :<name> )Yöntem adınızı çağrılabilir bir sembole dönüştürürken yalnızca bir kez çağırdığınızı belirtmek gerekir . Bu sonucu bir değişken veya parametrede saklayabilir ve bundan sonra diğer değişkenler gibi alt işlevlere

1
Veya belki, bağımsız değişken listesinde yöntem sözdizimini kullanmak yerine , yöntemi şu şekilde çağırırken kullanabilirsiniz: weightedknn (data, vec1, k, method (: gaussian))
Yahya

1
Bu yöntem, bir proc veya blokla uğraşmaktan daha iyidir, çünkü parametreleri işlemek zorunda değilsiniz - sadece yöntem ne isterse çalışır.
danuker

3
Tamamlamak için, başka bir yerde tanımlanmış bir yöntemi geçmek istiyorsanız, yapın SomewhereElse.method(:method_name). Bu oldukça havalı!
medik

Bu kendi sorusu olabilir, ancak bir sembolün bir işleve mi yoksa başka bir şeye mi başvurduğunu nasıl belirleyebilirim? Denedim :func.classama bu sadece varsymbol
quietContest

46

Bir yöntemi parametre olarak method(:function)yolla iletebilirsiniz . Aşağıda çok basit bir örnek var:

def double (a)
  a * 2 döndür 
son
=> sıfır

def method_with_function_as_param (geri arama, numara) 
  callback.call (sayı) 
son 
=> sıfır

method_with_function_as_param (yöntem (: çift), 10) 
=> 20

7
Daha karmaşık bir kapsamı olan bir yöntemle ilgili bir sorunla karşılaştım ve sonunda nasıl yapılacağını anladım, umarım bu birisine yardımcı olabilir: Yönteminiz örneğin başka bir sınıftaysa, son kod satırınımethod_with_function_as_param(Class.method(:method_name),...)method(:Class.method_name)
V

Cevabınız sayesinde denilen yöntemi keşfettim method. Günümü yaptım ama sanırım bu yüzden işlevsel dilleri tercih ediyorum, istediğinizi elde etmek için böyle akrobasi yapmaya gerek yok. Her neyse, yakut kazıyorum
Ludovic Kuty

25

Bunu yapmanın normal Ruby yolu bir blok kullanmaktır.

Yani şöyle bir şey olurdu:

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

Ve şu şekilde kullanılır:

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

Bu kalıp Ruby'de yaygın olarak kullanılmaktadır.



1

İşlev nesnesinin "call" yöntemini çağırmanız gerekir:

weight = weightf.call( dist )

DÜZENLEME: Yorumlarda açıklandığı gibi, bu yaklaşım yanlış. Normal işlevler yerine Procs kullanıyorsanız işe yarar.


1
Yaptığı zaman weightf = gaussianarg listesinde aslında yürütmeye çalışıyor gaussianve weightf varsayılan değer olarak sonucu atayın. Çağrının gerekli bağımsız değişkenleri ve kilitlenmeleri yok. Yani, weightf henüz bir çağrı yöntemine sahip bir proc nesnesi bile değildir.
Alex Wayne

1
Bu (yani yanlış yapmak ve nedenini açıklayan yorum) aslında kabul edilen cevabı tam olarak anlamamı sağladı, bu yüzden teşekkürler! +1
rmcsharry

1

Bir işlev içindeki adlandırılmış bloklara erişim sağlamak için "ve" işaretini kullanmanızı tavsiye ederim. Bu makalede verilen önerileri takip ederek şöyle bir şey yazabilirsiniz (bu benim çalışma programımdan gerçek bir nottur):

  # 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

Afterwerds, bu işlevi şu şekilde çağırabilirsiniz:

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

Seçiminizi filtrelemeniz gerekmiyorsa, bloğu atlamanız yeterlidir:

@categories = make_select_list( Category ) # selects all categories

Ruby bloklarının gücü için çok fazla.


-5

Ayrıca, "eval" öğesini kullanabilir ve yöntemi bir dizge bağımsız değişkeni olarak iletebilir ve ardından onu diğer yöntemde değerlendirebilirsiniz.


1
Bu gerçekten kötü bir uygulama, bunu asla yapma!
Geliştirici

@Developer bu neden kötü bir uygulama olarak kabul ediliyor?
jlesse

Performans nedenlerinin yanı sıra eval, keyfi kod çalıştırabilir , bu nedenle çeşitli saldırılara karşı son derece savunmasızdır.
Geliştirici
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.