본문 바로가기
Ruby on Rails

ruby의 반복문은 왜 이렇게 생겼을까

by 혜리루 2021. 4. 11.

1. 서론

저는 rails 개발을 할때 이런식의 반복문을 주로 사용합니다.

[1,2,3].each do |v]
  puts v
end

사실 지금까지는 이 문법이 왜 이런모양새일까 아무런 의심 없이 뇌를 안거치고 사용하고있었는데요

이번에 ruby의 기본 문법을 공부하면서 이 신비로운 모양새의 비밀을 알게되었습니다.

오늘 이 문법이 왜 이렇게 생겼는지 포스팅 해보려고합니다.

 

2. ruby의 반복문

 

먼저 ruby의 일반적인 반복문 문법부터 살펴보겠습니다,.

 

ruby에서도 다른 언어들과 마찬가지로 while과 for문의 사용이 가능합니다.

while true
    puts 'using while statement'
end

개인적으로 for문의 사용법은 python과 비슷하게 느껴지네요.

for i in 0..4
    puts "using for statement"
end

 

ruby에서는 이러한 while이나 for문보다는 제가 서론에서 언급한 iterator 문법을 주로 사용합니다.

 

3. iterator란?

 

each, map 등등 ror 개발을 하다보면 자연스럽게 iterator를 사용할 수 밖에 없는데요,

그래서 iterator란 도대체 뭐하는 녀석일까요?

사실 별 거 없습니다.

iterator는 collection 객체의 요소들을 하나씩 순회하면서 무엇인가의 기능을 하는 collection 객체의 instance method입니다.

( array나 hashe같은 자료구조들이 ruby의 collection 객체입니다. )

정말 별거 없죠? 정말 그냥 method입니다. 그런데 일반적인 method와 한가지 다른점이 있다면 block이 꼭 필요하다는 점입니다.

 

4. block

block은 iterator를 이해하기 위해서 꼭 알아야할 개념입니다.

간단히 얘기하자면 block은 do, end 혹은 {}로 감싸져있는 코드들의 집합입니다.

 

{ puts 'hello world' }

do
  puts 'hello'
  puts 'world'
end

보통 {}는 한줄짜리의 코드 집합에, do end는 두줄 이상의 코드집합에 사용합니다.

루비에서는 block을 이용해서 쉽게 익명함수를 만들고 이를 method로 전달해줄 수 있습니다.

block은 proc과 비슷한 개념이지만 method없이 존재할 수 없다는 점에서 다릅니다.

 

irb(main):017:0> { puts 'hi' }
Traceback (most recent call last):
        3: from /usr/bin/irb:23:in `<main>'
        2: from /usr/bin/irb:23:in `load'
        1: from /Library/Ruby/Gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
SyntaxError ((irb):17: syntax error, unexpected tSTRING_BEG, expecting do or '{' or '(')
...ise _; rescue _.class; { puts 'hi' }
...                              ^
(irb):17: syntax error, unexpected '}', expecting end
...; rescue _.class; { puts 'hi' }

이렇게 독립적으로 사용하려고하면 exception이 발생합니다.

 

5. 구조

 

다시 원래 주제로 돌아와서 최초에 언급했던 문법을 하나씩 살펴보겠습니다.

[1,2,3].each do |v]
  puts v
end

iterator는 단순히 collection 의 instance method라고 했죠.

위의 코드는 [1,2,3]이라는 collection(array)의 instance method인 each를 호출하고 있다는 것을 알 수 있습니다.

그 뒤에는 값을 출력하는 간단한 block이 자리잡고 있습니다. 

그리고 우리는 method에 block을 전달 할 수 있다는 것을 알고 있습니다.

 

이제 그림이 보이시나요?

 

 

저는 막연했던 코드가 이제서야 확실하고 쉽게 보이네요.

이 문법은 block을 parameter처럼 전달하면서 each 함수를 호출하는 간단한 구조였네요.

 

그리고 그 each 메소드의 내부를 살짝 뜯어보면 ruby의 yield문이 포함되어 있습니다.

 

  def each
    # This is a stub implementation, used for type inference (actual method behavior may differ)
    block_given? ? (yield to_enum.next; self) : to_enum
  end

 

yield는 함수형 프로그래밍에서 자주 나오는 구문인데요, 간단하게 말하면 method가 전달받은 block의 코드가 실행되는 타이밍이라고 보시면 됩니다.

 

더 간단한 코드로 살펴보겠습니다.

 

def call_block
  puts 'start'
  yield
  yield
  puts 'end'
end

call_block { puts 'haha' }
> start
> haha
> haha
> end

 

이런식으로 method의 코드 실행상에서 yield 구문을 만났을때 block의 코드가 실행됩니다.

 

그래도 아직 찝찝한 부분이 남아있죠. block안의 local variable은 어떻게 존재하는 것일까요?

우리는 yield 구문에 함수처럼 parameter를 넘겨줄 수 있습니다. 그리고 이 parameter는 순서대로 block에 전달이 됩니다.

 

 

def call_block
  puts 'start'
  yield('hi')
  puts 'end'
end

call_block {|greet| puts "#{greet} haeree"}
> start
> hi haeree
> end
> => nil

 

이렇게 뜯어서 보니 정말 쉽고 명료하네요. 이제 ruby의 iterator를 쓸 때 마음편하게 쓸 수 있겠어요.

 

 

출처: www.cs.uni.edu/~wallingf/teaching/agile-may2010/ruby/programming-ruby.pdf

댓글