Ruby에서 매개 변수로 메소드 전달


117

루비를 조금 엉망으로 만들려고합니다. 그래서 나는 책 "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

이와 같은 블록 선언에서 기본 인수를 설정할 수 없습니다. 따라서 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)send 로 수행하려면 foo.send(:bar, a, b). splat (*) 연산자를 사용하면 foo.send(:bar, *[a,b])임의의 길이의 인수 배열을 원할 경우 수행 할 수 있습니다. bar 메서드가 인수를 흡수 할 수 있다고 가정하면
— xxjjnn

99

블록과 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 또는 블록을 사용하는 것보다 낫습니다. 방법이 원하는대로 작동합니다.
— danuker

3
완료를 위해 다른 곳에 정의 된 메서드를 전달하려면 SomewhereElse.method(:method_name). 꽤 괜찮은데!
— medik

이것은 자체 질문 일 수 있지만 기호가 함수 또는 다른 것을 참조하는지 어떻게 확인할 수 있습니까? 나는 시도 :func.class하지만 그건 단지입니다symbol
— quietContest

46

방법으로 매개 변수로 메소드를 전달할 수 있습니다 method(:function). 다음은 매우 간단한 예입니다.

def double (a)
  * 2 반환 
종료
=> 없음

def method_with_function_as_param (콜백, 번호) 
  callback.call (번호) 
종료 
=> 없음

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é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에서 광범위하게 사용됩니다.



1

함수 객체의 "call"메소드를 호출해야합니다.

weight = weightf.call( dist )

편집 : 의견에 설명 된대로이 접근 방식은 잘못되었습니다. 정상적인 기능 대신 Procs를 사용하는 경우 작동합니다.


1
그가 weightf = gaussianarg 목록에서 할 때 실제로 gaussian는 그 결과를 weightf의 기본값으로 실행 하고 할당 하려고합니다 . 호출에는 필수 인수 및 충돌이 없습니다. 따라서 weightf는 아직 call 메소드가있는 proc 객체가 아닙니다.
— Alex Wayne

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

Ruby 블록의 힘을 위해 너무나 많이.


-5

"eval"을 사용하고 메서드를 문자열 인수로 전달한 다음 다른 메서드에서 간단히 평가할 수도 있습니다.


1
이것은 정말 나쁜 습관입니다. 절대하지 마세요!
— 개발자

@Developer 이것이 나쁜 습관으로 간주되는 이유는 무엇입니까?
— jlesse

성능상의 이유로 eval임의의 코드를 실행할 수 있으므로 다양한 공격에 매우 취약합니다.
— 개발자
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.