ส่งผ่านเมธอดเป็นพารามิเตอร์ใน Ruby


119

ฉันพยายามจะยุ่งกับรูบี้เล็กน้อย ดังนั้นฉันจึงพยายามใช้อัลกอริทึม (ให้ไว้ใน Python) จากหนังสือ "Programming Collective Intelligence" Ruby

ในบทที่ 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

โปรดทราบว่าคุณไม่สามารถตั้งค่าอาร์กิวเมนต์เริ่มต้นในการประกาศบล็อกเช่นนั้นได้ ดังนั้นคุณต้องใช้ splat และตั้งค่าเริ่มต้นในรหัส 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
ฉันคิดว่าตัวเลือกที่สองเป็นตัวเลือกที่ดีที่สุด (นั่นคือการใช้ Object.send ()) ข้อเสียเปรียบคือคุณต้องใช้คลาสทั้งหมด (ซึ่งเป็นวิธีที่คุณควรทำใน OO ต่อไป :)) มันแห้งกว่าการส่งบล็อก (Proc) ตลอดเวลาและคุณยังสามารถส่งผ่านอาร์กิวเมนต์ผ่านวิธีการห่อหุ้ม
Jimmy Stenke

4
ในฐานะที่เป็นนอกจากนี้ถ้าคุณต้องการที่จะทำกับส่งมันเป็นfoo.bar(a,b) foo.send(:bar, a, b)ตัวดำเนินการ splat (*) ช่วยให้คุณสามารถทำได้foo.send(:bar, *[a,b])หากคุณพบว่าคุณต้องการมีอาร์เรย์ของอาร์กิวเมนต์ที่มีความยาวโดยพลการ - สมมติว่าวิธีแถบสามารถดูด
ซับได้

100

ความคิดเห็นที่อ้างถึงบล็อกและ Procs นั้นถูกต้องเนื่องจากมีความปกติมากกว่าใน 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 หรือ block เนื่องจากคุณไม่จำเป็นต้องจัดการกับพารามิเตอร์ - ใช้ได้กับทุกวิธีที่ต้องการ
danuker

3
SomewhereElse.method(:method_name)สำหรับการเสร็จสิ้นถ้าคุณต้องการที่จะผ่านวิธีการที่กำหนดไว้ที่อื่นทำ เจ๋งมาก!
medik

นี่อาจเป็นคำถามของตัวเอง แต่ฉันจะทราบได้อย่างไรว่าสัญลักษณ์อ้างอิงถึงฟังก์ชันหรืออย่างอื่น? ฉันพยายามแล้ว:func.classแต่นั่นเป็นเพียงsymbol
quietContest

47

คุณสามารถส่งเมธอดเป็นพารามิเตอร์ด้วยmethod(:function)วิธี ด้านล่างนี้เป็นตัวอย่างง่ายๆ:

def คู่ (a)
  ส่งคืน a * 2 
ปลาย
=> ศูนย์

def method_with_function_as_param (โทรกลับหมายเลข) 
  callback.call (ตัวเลข) 
ปลาย 
=> ศูนย์

method_with_function_as_param (วิธีการ (: สองครั้ง), 10) 
=> 20

7
ฉันประสบปัญหาสำหรับวิธีการที่มีขอบเขตที่ซับซ้อนกว่านี้และในที่สุดก็คิดว่าจะทำอย่างไรหวังว่านี่จะช่วยใครบางคนได้: หากวิธีการของคุณเป็นตัวอย่างในคลาสอื่นคุณควรเรียกโค้ดบรรทัดสุดท้ายว่าmethod_with_function_as_param(Class.method(:method_name),...)และไม่ใช่method(:Class.method_name)
V . Déhaye

methodขอบคุณกับคำตอบของคุณที่ผมค้นพบวิธีการที่เรียกว่า ทำวันของฉัน แต่ฉันเดาว่านั่นเป็นเหตุผลที่ฉันชอบภาษาที่ใช้งานได้ไม่จำเป็นต้องแสดงผาดโผนเพื่อให้ได้สิ่งที่คุณต้องการ อย่างไรก็ตามฉันขุดทับทิม
Ludovic Kuty

25

วิธีปกติของ Ruby คือการใช้บล็อก

ดังนั้นมันจะเป็นดังนี้:

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

และใช้เช่น:

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

รูปแบบนี้ถูกใช้อย่างกว้างขวางใน Ruby


14

คุณสามารถใช้&ประกอบการในMethodตัวอย่างของวิธีการของคุณที่จะแปลงวิธีการที่จะกระชาก

ตัวอย่าง:

def foo(arg)
  p arg
end

def bar(&block)
  p 'bar'
  block.call('foo')
end

bar(&method(:foo))

รายละเอียดเพิ่มเติมที่http://weblog.raganwald.com/2008/06/what-does-do-when-used-as-unary.html


1

คุณต้องเรียกเมธอด "โทร" ของวัตถุฟังก์ชัน:

weight = weightf.call( dist )

แก้ไข: ตามที่อธิบายไว้ในความคิดเห็นแนวทางนี้ไม่ถูกต้อง จะใช้ได้ถ้าคุณใช้ Procs แทนฟังก์ชันปกติ


1
เมื่อเขาทำweightf = gaussianในรายการอาร์กิวเมนต์มันจะพยายามดำเนินการgaussianและกำหนดผลลัพธ์เป็นค่าเริ่มต้นของ weightf การโทรไม่จำเป็นต้องมีอาร์กิวเมนต์และข้อขัดข้อง ดังนั้น weightf จึงยังไม่ใช่วัตถุ proc ด้วยวิธีการโทร
Alex Wayne

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

Afterwerds คุณสามารถเรียกใช้ฟังก์ชันนี้ได้ดังนี้:

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

หากคุณไม่จำเป็นต้องกรองสิ่งที่คุณเลือกคุณก็ละเว้นบล็อก:

@categories = make_select_list( Category ) # selects all categories

มากสำหรับพลังของบล็อกรูบี้


-5

คุณยังสามารถใช้ "eval" และส่งผ่านเมธอดเป็นอาร์กิวเมนต์สตริงจากนั้นจึงประเมินด้วยวิธีอื่น


1
นี่เป็นการปฏิบัติที่ไม่ดีจริงๆอย่าทำ!
พัฒนา

@ ผู้พัฒนาเหตุใดจึงถือว่าเป็นการปฏิบัติที่ไม่ดี
jlesse

ด้วยเหตุผลด้านประสิทธิภาพการevalทำงานสามารถรันโค้ดได้ตามอำเภอใจจึงมีความเสี่ยงอย่างมากต่อการโจมตีต่างๆ
พัฒนา
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.