ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Programming Ruby (8) 트랜잭션을 위한 블록, 객체로서의 블록
    Ruby 2016. 10. 24. 23:50
    [출처] Programming Ruby 
    (본 게시물은 저작권의 문제 발생시 출판사의 요청에 의해 삭제될 수 있습니다.)


    트랜잭션을 위한 블록 


    트랜잭션 제어 하에서 동작해야 하는 코드를 작성할때 블록을 사용할 수 있다. 예를 들어 파일을 열고 그내용으로 어떤 작업을 하고 이 작업을 마치면 이 파일이 닫힌다는 사실을 확신하고 싶다고 하자. 블록을 사용하는 방식은 단순하게 표현할수 있다. 다음 예제는 예외처리를 포함하지 않는 간단한 구현을 보여준다.


    class File

         def self.open_and_process(*args)

              f = File.open(*args)

              yield f

              f.close()

         end

    end


    File.open_and_process("testfile", "r") do |file|

         while line =file.gets

              puts line

         end

    end


    실행결과

    This is line one

    This is line two

    This is line three

    And so on. ... 


    open_and_process는 클래스 메서드다 특정 파일 객체에 상관없이 호출할 수 있다. 이 메서드가 일반적인 File.open 메서드와 똑같은 매개 변수를 받는다면 편리할 것이다. 메서드 정의 부분에 *args라고 쓰고 그 다음에는 File.open 을 호출해서 *args 를 매개변수로 넘긴다. 따라서 결과적으로 open_and_process 메서드가 받은 매개 변수를 투명하게 File.open으로 넘겨주게 된다. 

    파일을 열었다면 open_and_process메서드는 yield를 호출해 방금 연 파일 객체를ㄹ 블록 객체에 넘겨준다. 그리고 블록 실행이 종료되면 파일을 닫는다. 열린 파일의 핸들을 닫아야 하는 책임이 파일 객체의 사용자로부터 파일 객체 자체로 넘어가게 된다. 





    객체로서의 블록


    블록은 익명 메서드와 비슷하지만 단순히 이걸로 끝은 아니다. 블록은 객체로 변환할 수 있으며, 이 객체를 변수에 저장할수도 있고 어딘가에 넘겨줄 수도 있으며 나중에 호출할 수도 있다. 

    앞서 블록은 메서드에 넘겨지는 추가적인 매개 변수라고 생각해도 무방하다고 이야기한 바 있다. 메서드를 정의할 때 마지막 매개 변수에 앰퍼센드(&)를 접두사로 붙이면(&action) 루비는 메서드가 호출될 때마다 코드 블록이 넘겨졌는지 찾아본다. 이 코드 블록은 Proc 클래스의 객체로 변환되어 매개 변수로 넘겨진다. 


    다음은 Proc 객체를 생성하고 인스턴스 변수에 저장하고, 다른 인스턴스 메서드에서 이를 호출하는 예제다



    class ProcExample

         def pass_in_block(&action)

              @stored_proc = action

         end

         def use_proc(parameter)

              @stored_proc.call(parameter)

         end

    end



    eg = ProcExample.new

    eg.pass_in_block { |Param| puts "the parameter is #{param}}

    eg.use_bloc(99)


    실행결과

    the parameter is 99 



    proc 객체에 대해 call 메서드를 호출함으로써 원래의 블록을 호출한다는 점에 주의할 필요가 있다. 많은 루비프로그래머들이 이러한 방식으로 블록을 변수에 저장하며 이후에 다시 호출한다. 


    메서드에서 매개 변수 앞에 앰퍼센드를 붙여 블록을 객체로 변환할수 있다면, 이 메서드에서 Proc 객체를 실행하고 그 결과를 다시 반환한다면 어떻게 될까?


    def create_block_object(&block)

         block

    end


    bo = craete_block_object { |param| puts "You called me with #{param} " } 


    bo.call 99

    bo.call "cat"


    실행결과

    You called me with 99

    You called me with cat


    루비에서는 블록을 객체로 변환하는 내장 메서드를 두개나 지원한다.  lambda와 Proc.new 가 바로 그것이다. 두 메서드는 블록을 받아 Proc 객체를 반환한다. 


    bo = lambda { |param| puts "You called me with #{param}" }

    bo.call 99

    bo.call "cat"


    실행결과

    You called me with 99

    You called me with cat




    블록은 클로저이기도 하다


    블록 내부에서 블록의 외부 스코프에 있는 지역 변수도 참조 가능하다는 설명을 했다. 다음과 같이 특이하게 블록을 활용할 수 있다.


    def n_times(thing)

         lambda { | n | thing * n } 

    end


    p1 = n_times(23)

    p1.call(3) # => 69

    p1.call(4) # => 92

    p2 = n_times("Hello ")

    p2.call(3) # => "Hello Hello Hello "


    n_times 메서드는 이 메서드이 매개 변수 thing 을 참조하는 Proc 객체를 반환한다. 이 매개변수는 블록이 호출될때 스코프 범위 밖에 있지만 블록에서는 당연한 듯 사용할 수 있다. 이를 클로저라 한다. 블록 내부에서 참조하고 있는 변수는 블록을 벗어난 시점에서도 이 블록이 존재하는 한, 또는 이 블록에서 생성된 Proc 객체가 존재하는 한 언제든지 접근 가능하다.


    2의 제곱열을 반환하는 Proc 객체를 반환하는 예제이다.


    def power_proc_generator

         value = 1

         lambda { value += value }

    end


    power_proc = power_proc_generator


    puts power_proc.call

    puts power_proc.call

    puts power_proc.call


    실행결과

    2

    4

    8




    대체문법 


    루비는 Proc 객체를 생성하기 위한 또다른 문법을 제공한다. 

    lambda { |param| ... } 은


    -> params { ...}  


    와 같이 간결하게 작성할 수있다.


    매개 변수는 괄호로 감싸도 되고 감싸지 않아도 된다.


    proc1 = -> arg { puts "In proc1 with #{arg}" {

    proc2 = -> arg1, arg2 { puts "#{arg1} and #{arg2}" }

    proc3 = -> (arg1, arg2) { puts "#{arg1} and #{arg2}" }


    proc1.call "ant"

    proc2.call "bee", "cat"

    proc3.call "dog", "elk"


    실행결과

    In proc1 with ant

    bee and cat

    dog and elk 



    -> 형식이 lambda 문법보다 간결하다. 하나 이상의 Proc 객체를 메서드에 넘겨줄 땐 -> 형식이 선호된다.


    def my_if(condition, then_clause, else_clause)

         if condition

              then_clause.call

         else

              else_clause.call

         end

    end


    5.times do |val|

         my_if val < 2,

              -> {puts "#{val} is small" },

              -> {puts "#{val} is big" }

    end


    실행결과

    0 is small

    1 is small

    2 is big

    3 is big

    4 is big 



    메서드에 블록을 넘겨주는 이유는 블록 내부의 코드를 언제라도 다시 평가할 수 있다는 점이다. 다음은 while 문을 다시 구현하는 예제이다. 반복조건을 블록으로 넘겨주기 때문에 반복될 때마다 매번 평가된다.


    def my_while(cond, &body)

         while cond.call

              body.call

         end

    end



    a = 0


    my_while -> {a < 3 } do

         puts a

         a += 1

    end


    실행결과

    0

    1

    2





    블록의 매개변수 리스트 


    예전부터 사용되어 오던 문법에서는 블록이 매개 변수를 받으면 막대문자( | )를 사용해서 나타냈다. -> 문법을 사용할 때는 블록의 본문에 앞서 매개 변수 리스트를 열거한다. 어느 쪽이건 메서드를 넘기는 것과 마찬가지 매개 변수 리스트를 정의할 수 있다. 메서드와 마찬가지로 기본값, 가변 길이 인자, 키워드인자, 블록 매개변수 모두 사용할 수 있다. 블록 매개 변수 모두 사용할 수 있다.


    proc1 = lambda do | a, *b, &block |

         puts "a = #{a.inspect}"

         puts "b = #{b.inspect}"

         block.call

    end


    proc1.call (1, 2, 3, 4) {puts "in block1" }


    실행결과

    a = 1

    b = [2, 3, 4]

    in block1



    새로운 -> 문법을 사용한 예제다


    proc2 = -> a, *b, &block do

         puts "a = #{a.inspect}"

         puts "b = #{b.inspect}"

         block.call

    end 


    proc2.call(1, 2, 3, 4) {puts "in block2" }


    실행 결과

    a = 1

    b = [ 2, 3, 4]

    in block2





    끄읕.



Designed by Tistory.