[ruby] Ruby 블록에서 ‘return’사용

임베디드 스크립팅 언어로 Ruby 1.9.1을 사용하여 “최종 사용자”코드를 Ruby 블록에 작성하려고합니다. 이것의 한 가지 문제는 사용자가 블록에서 ‘return’키워드를 사용할 수 있기를 원하므로 암시 적 반환 값에 대해 걱정할 필요가 없습니다. 이를 염두에두고 다음과 같이 할 수 있기를 바랍니다.

def thing(*args, &block)
  value = block.call
  puts "value=#{value}"
end

thing {
  return 6 * 7
}

위의 예에서 ‘return’을 사용하면 LocalJumpError가 발생합니다. 나는 이것이 문제의 블록이 람다가 아니라 Proc이기 때문이라는 것을 알고 있습니다. ‘return’을 제거하면 코드가 작동하지만이 시나리오에서는 ‘return’을 사용할 수 있기를 원합니다. 이것이 가능한가? 블록을 람다로 변환하려고 시도했지만 결과는 동일합니다.



답변

next이 컨텍스트에서 간단히 사용 하십시오.

$ irb
irb(main):001:0> def thing(*args, &block)
irb(main):002:1>   value = block.call
irb(main):003:1>   puts "value=#{value}"
irb(main):004:1> end
=> nil
irb(main):005:0>
irb(main):006:0* thing {
irb(main):007:1*   return 6 * 7
irb(main):008:1> }
LocalJumpError: unexpected return
        from (irb):7:in `block in irb_binding'
        from (irb):2:in `call'
        from (irb):2:in `thing'
        from (irb):6
        from /home/mirko/.rvm/rubies/ruby-1.9.1-p378/bin/irb:15:in `<main>'
irb(main):009:0> thing { break 6 * 7 }
=> 42
irb(main):011:0> thing { next 6 * 7 }
value=42
=> nil
  • return 항상 메서드에서 반환하지만 irb에서이 스 니펫을 테스트하면 메서드가 없기 때문에 LocalJumpError
  • break블록에서 값을 반환하고 호출을 종료합니다. yield또는 .call에서 블록을 호출 한 break경우이 반복자에서도 중단됩니다.
  • next블록에서 값을 반환하고 호출을 종료합니다. 당신의 블록에 의해 호출 된 경우 yield또는 .call다음 next라인에 값 반환 yield불렸다을


답변

Ruby에서는 그렇게 할 수 없습니다.

return키워드는 항상 현재 컨텍스트의 방법 또는 람다에서 반환합니다. 블록에서는 클로저가 정의 된 메서드에서 반환 됩니다 . 부름 에서 돌아올 수 없습니다 메서드 또는 람다 .

Rubyspec은 이 루비에 대한 올바른 동작 (일반적으로 인정 하듯이 아닌 실제 구현하지만, 목표 C 루비와 완벽한 호환성)이 참임을 보여줍니다

describe "The return keyword" do
# ...
describe "within a block" do
# ...
it "causes the method that lexically encloses the block to return" do
# ...
it "returns from the lexically enclosing method even in case of chained calls" do
# ...


답변

잘못된 관점에서보고 있습니다. 이것은 thing람다가 아니라의 문제입니다 .

def thing(*args, &block)
  block.call.tap do |value|
    puts "value=#{value}"
  end
end

thing {
  6 * 7
}


답변

물건은 어디에서 호출됩니까? 수업 안에 있습니까?

다음과 같은 것을 사용하는 것이 좋습니다.

class MyThing
  def ret b
    @retval = b
  end

  def thing(*args, &block)
    implicit = block.call
    value = @retval || implicit
    puts "value=#{value}"
  end

  def example1
    thing do
      ret 5 * 6
      4
    end
  end

  def example2
    thing do
      5 * 6
    end
  end
end


답변

루비에서 웹 프레임 워크 용 DSL을 작성하는 데 동일한 문제가 발생했습니다 … (웹 프레임 워크 Anorexic이 흔들릴 것입니다!) …

어쨌든, 나는 루비 내부를 파헤쳐 서 Proc가 return을 호출 할 때 반환 된 LocalJumpError를 사용하여 간단한 해결책을 찾았습니다 … 지금까지 테스트에서 잘 실행되지만 완전한 증거인지는 모르겠습니다.

def thing(*args, &block)
  if block
    block_response = nil
    begin
      block_response = block.call
    rescue Exception => e
       if e.message == "unexpected return"
          block_response = e.exit_value
       else
          raise e
       end
    end
    puts "value=#{block_response}"
  else
    puts "no block given"
  end
end

구조 세그먼트의 if 문은 다음과 같을 수 있습니다.

if e.is_a? LocalJumpError

하지만 저에게는 미지의 영역이므로 지금까지 테스트 한 내용을 고수하겠습니다.


답변

단점에도 불구하고 이것이 정답이라고 생각합니다.

def return_wrap(&block)
  Thread.new { return yield }.join
rescue LocalJumpError => ex
  ex.exit_value
end

def thing(*args, &block)
  value = return_wrap(&block)
  puts "value=#{value}"
end

thing {
  return 6 * 7
}

이 해킹을 통해 사용자는 결과없이 자신의 procs에서 return을 사용할 수 있습니다.

여기서 Thread를 사용하는 장점은 어떤 경우에는 LocalJumpError가 발생하지 않고 가장 예상치 못한 곳에서 반환이 발생한다는 것입니다 (최상위 메서드에서 예기치 않게 나머지 본문을 건너 뜁니다).

가장 큰 단점은 잠재적 인 오버 헤드입니다 ( yield시나리오에서 충분한 경우 에만 Thread + join을 대체 할 수 있음).


답변

방법을 찾았지만 방법을 중간 단계로 정의하는 것이 포함됩니다.

def thing(*args, &block)
  define_method(:__thing, &block)
  puts "value=#{__thing}"
end

thing { return 6 * 7 }