Truyền một phương thức làm tham số trong Ruby


117

Tôi đang cố gắng gây rối một chút với Ruby. Vì vậy, tôi cố gắng triển khai các thuật toán (được đưa ra bằng Python) từ cuốn sách "Lập trình trí tuệ tập thể" Ruby.

Trong chương 8, tác giả chuyển một phương thức a dưới dạng tham số. Điều này dường như hoạt động trong Python nhưng không hoạt động trong Ruby.

Tôi có ở đây phương pháp

def gaussian(dist, sigma=10.0)
  foo
end

và muốn gọi điều này bằng một phương thức khác

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

Tất cả những gì tôi nhận được là một lỗi

ArgumentError: wrong number of arguments (0 for 1)

Câu trả lời:


100

Bạn muốn một đối tượng 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

Chỉ cần lưu ý rằng bạn không thể đặt đối số mặc định trong khai báo khối như vậy. Vì vậy, bạn cần sử dụng một biểu tượng và thiết lập mặc định trong mã proc.


Hoặc, tùy thuộc vào phạm vi của bạn về tất cả điều này, có thể dễ dàng hơn để chuyển vào tên phương thức thay thế.

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

Trong trường hợp này, bạn chỉ đang gọi một phương thức được xác định trên một đối tượng chứ không phải truyền một đoạn mã hoàn chỉnh. Tùy thuộc vào cách bạn cấu trúc, bạn có thể cần thay thế self.sendbằngobject_that_has_the_these_math_methods.send


Cuối cùng nhưng không kém phần quan trọng, bạn có thể treo một khối khỏi phương thức.

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

Nhưng có vẻ như bạn muốn nhiều đoạn mã có thể tái sử dụng hơn ở đây.


1
Tôi nghĩ rằng tùy chọn thứ hai là tùy chọn tốt nhất (nghĩa là, sử dụng Object.send ()), hạn chế là bạn cần phải sử dụng một lớp cho tất cả nó (đó là cách bạn nên làm trong OO :)). Nó KHÔ hơn là truyền một khối (Proc) mọi lúc, và bạn thậm chí có thể truyền các đối số vượt qua phương thức wrapper.
Jimmy Stenke

4
Như một phần bổ sung, nếu bạn muốn làm foo.bar(a,b)với gửi, nó là foo.send(:bar, a, b). Các splat (*) điều hành cho phép bạn làm foo.send(:bar, *[a,b]), bạn nên tìm bạn muốn có một mảng lengthed tùy tiện của các đối số - giả sử dụng phương pháp thanh có thể ngâm chúng
xxjjnn

99

Các nhận xét đề cập đến các khối và Procs là đúng ở chỗ chúng thường thấy hơn trong Ruby. Nhưng bạn có thể chuyển một phương thức nếu bạn muốn. Bạn gọi methodđể lấy phương thức và .callgọi nó:

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

3
Hay đấy. Cần lưu ý rằng bạn method( :<name> )chỉ gọi một lần khi chuyển đổi tên phương thức của bạn thành một biểu tượng có thể gọi được. Bạn có thể lưu kết quả trong một biến hoặc một tham số và giữ đi qua nó để chức năng con như bất kỳ biến khác từ đó trở đi ...

1
Hoặc có thể, thay vì sử dụng các phương pháp cú pháp trong danh sách đối số, bạn có thể sử dụng nó trong khi cách gọi phương pháp này như sau: weightedknn (dữ liệu, vec1, k, phương pháp (: gaussian))
Yahya

1
Phương pháp này tốt hơn so với việc sử dụng proc hoặc khối, vì bạn không phải xử lý các tham số - nó chỉ hoạt động với bất kỳ phương thức nào muốn.
danuker

3
Để hoàn thành, nếu bạn muốn chuyển một phương thức được định nghĩa ở một nơi khác, hãy làm SomewhereElse.method(:method_name). Điều đó khá tuyệt!
medik

Đây có thể là câu hỏi của riêng nó, nhưng làm thế nào tôi có thể xác định xem một biểu tượng có tham chiếu đến một hàm hay cái gì khác không? Tôi đã thử :func.classnhưng đó chỉ là mộtsymbol
yên tĩnh. Thử nghiệm vào

46

Bạn có thể truyền một phương thức làm tham số với method(:function)cách. Dưới đây là một ví dụ rất đơn giản:

def kép (a)
  trả về một * 2 
kết thúc
=> nil

def method_with_ functions_as_param (callback, number) 
  callback.call (số) 
kết thúc 
=> nil

method_with_ functions_as_param (method (: double), 10) 
=> 20

7
Tôi đã gặp sự cố đối với một phương thức có phạm vi phức tạp hơn và cuối cùng đã tìm ra cách thực hiện, hy vọng điều này có thể giúp ích cho ai đó: Ví dụ: nếu phương thức của bạn nằm trong một lớp khác, bạn nên gọi dòng mã cuối cùng giống như method_with_function_as_param(Class.method(:method_name),...)và khôngmethod(:Class.method_name)
V . Déhaye

Nhờ câu trả lời của bạn, tôi đã khám phá ra phương pháp được gọi là method. Đã đến ngày của tôi nhưng tôi đoán đó là lý do tại sao tôi thích các ngôn ngữ chức năng hơn, không cần phải nhào lộn như vậy để đạt được những gì bạn muốn. Dù sao thì tôi cũng đào được ruby
Ludovic Kuty

25

Cách thông thường của Ruby để làm điều này là sử dụng một khối.

Vì vậy, nó sẽ giống như:

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

Và được sử dụng như:

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

Mẫu này được sử dụng nhiều trong Ruby.



1

Bạn phải gọi phương thức "gọi" của đối tượng hàm:

weight = weightf.call( dist )

CHỈNH SỬA: như đã giải thích trong các nhận xét, cách tiếp cận này là sai. Nó sẽ hoạt động nếu bạn đang sử dụng Procs thay vì các chức năng bình thường.


1
Khi anh ta thực hiện weightf = gaussiantrong danh sách đối số, nó thực sự đang cố gắng thực thi gaussianvà gán kết quả làm giá trị mặc định của weightf. Cuộc gọi không có args bắt buộc và sự cố. Vì vậy, weightf thậm chí không phải là một đối tượng proc với một phương thức gọi.
Alex Wayne

1
Điều này (tức là làm sai và nhận xét giải thích lý do) thực sự cho phép tôi hiểu đầy đủ câu trả lời được chấp nhận, vì vậy cảm ơn! +1
rmcsharry

1

Tôi khuyên bạn nên sử dụng ký hiệu và để có quyền truy cập vào các khối được đặt tên trong một hàm. Theo các khuyến nghị được đưa ra trong bài viết này, bạn có thể viết một cái gì đó như thế này (đây là một mẩu tin lưu niệm thực sự từ chương trình làm việc của tôi):

  # 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, bạn có thể gọi hàm này như sau:

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

Nếu bạn không cần lọc lựa chọn của mình, bạn chỉ cần bỏ qua khối:

@categories = make_select_list( Category ) # selects all categories

Quá nhiều cho sức mạnh của các khối Ruby.


-5

bạn cũng có thể sử dụng "eval" và truyền phương thức dưới dạng đối số chuỗi, sau đó chỉ cần đánh giá nó trong phương thức khác.


1
Đây thực sự là một thực hành không tốt, đừng bao giờ làm điều đó!
Nhà phát triển

@Developer tại sao điều này được coi là thực hành xấu?
jlesse

Trong số các lý do về hiệu suất, mã evalcó thể thực thi mã tùy ý vì vậy nó rất dễ bị tấn công.
Nhà phát triển
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.