루비(Ruby)의 숨겨진 멋진 트릭들을 찾고 계신가요?
제대로 찾아오셨습니다!
이 글에서는 제가 특히 아끼는 루비 트릭들을 하나씩 소개해 드립니다.
1. 깊은 복사(Deep Copy)
Array처럼 다른 객체를 포함하는 객체를 clone으로 복사하면, 내부 객체들은 실제로 복사되는 것이 아니라 참조(reference)만 복사됩니다.
다음 예시를 직접 확인해 보세요:
food = %w( bread milk orange ) food.map(&:object_id) # [35401044, 35401020, 35400996] food.clone.map(&:object_id) # [35401044, 35401020, 35400996] ← 동일한 객체들
원래 직렬화(serialization)에 사용되는 Marshal 클래스를 활용하면 객체의 '깊은 복사(deep copy)'를 만들 수 있습니다.
def deep_copy(obj) Marshal.load(Marshal.dump(obj)) end
결과는 다음과 같습니다:
deep_copy(food).map(&:object_id) # [42975648, 42975624, 42975612] ← 완전히 새로운 객체들
2. 람다(Lambda)를 호출하는 다양한 방법
my_lambda = -> { puts 'Hello' }
my_lambda.call
my_lambda[]
my_lambda.()
my_lambda.===
가능하면 첫 번째 방식인 call을 사용하는 것이 좋습니다. 대부분의 개발자가 이 방법에 가장 익숙하기 때문입니다.
3. 초기값이 채워진 배열 만들기
Array 클래스는 인수와 블록을 함께 받을 수 있어서, n개의 요소를 가진 배열을 손쉽게 생성할 수 있습니다. 기본적으로 요소는 nil로 채워지지만, 블록을 넘기면 블록의 반환값으로 배열이 채워집니다.
예시:
Array.new(10) { rand 300 }
위 코드는 0부터 299 사이의 무작위 숫자 10개로 이루어진 배열을 생성합니다.
4. true, false, nil도 객체다
true.class # TrueClass false.class # FalseClass nil.class # NilClass
이 객체들은 각각 단 하나의 복사본만 존재하며, 애써도 새로 만들어낼 수 없습니다.
바로 싱글턴(singleton) 패턴이 실제로 적용된 모습입니다.
5. 람다는 인수에 엄격하고, Proc은 관대하다
my_lambda = ->(a, b) { a + b }
my_proc = Proc.new { |a, b| a + b }
my_lambda.call(2)
# ArgumentError: wrong number of arguments (1 for 2)
my_proc.call(2)
# TypeError: nil can't be coerced into Fixnum
람다는 전달된 인수의 개수를 엄격하게 검사하지만, Proc은 누락된 인수를 nil로 처리할 뿐 자체적으로는 오류를 발생시키지 않습니다(물론 이후 연산 과정에서 문제가 생길 수 있습니다).
6. irb나 파일 없이 코드 바로 실행하기
ruby 명령어에는 흥미로운 옵션이 여러 가지 있습니다.
예를 들어 -e 플래그를 사용하면 코드 조각을 직접 넘겨 즉시 실행할 수 있습니다.
ruby -e '5.times { puts "Fun with Ruby" }'
-h 플래그를 사용하면 더 많은 옵션을 확인할 수 있습니다.
7. 한 줄 명령으로 만드는 나만의 미니 irb
irb가 어떻게 동작하는지 궁금해 본 적 있으신가요? 아래 명령어가 바로 그 초간단 버전입니다.
'REPL'이 무엇의 약자인지 먼저 기억해 보세요. Read-Eval-Print Loop, 즉 읽고-평가하고-출력하는 반복문입니다.
ruby -n -e 'p eval($_)'
프롬프트는 표시되지 않지만, 그냥 루비 코드를 입력해 보세요.
"A" * 5 # "AAAAA"
이것이 가능한 이유는 -n 플래그가 다음과 같이 동작하기 때문입니다:
-n assume 'while gets(); ... end' loop around your script
그리고 $_는 전역 변수로, 다음 값을 담고 있습니다:
gets 또는 readline이 마지막으로 읽어 들인 입력 줄(문자열)
8. 객체의 freeze 해제하기(위험!)
루비에는 객체의 freeze를 해제하는 공식 메서드가 없지만, Fiddle 클래스를 사용하면 루비 내부까지 파고들어 이를 강제로 해제할 수 있습니다.
require 'fiddle' str = 'water'.freeze str.frozen? # true memory_address = str.object_id * 2 Fiddle::Pointer.new(memory_address)[1] &= ~8 str.frozen? # false
절대 실전에서 따라 하지 마세요!
9. 특별한 object_id를 가진 객체들
루비 객체는 object_id 메서드로 접근할 수 있는 고유 식별자(id)를 가지고 있습니다. 그런데 일부 객체는 id가 고정되어 있습니다. 정수(Fixnum), true, false, nil이 바로 그렇습니다.
false.object_id # 0 true.object_id # 2 nil.object_id # 4 1.object_id # 3 2.object_id # 5
정수의 id는 (숫자 × 2) + 1 공식으로 계산됩니다.
보너스 팁: 정수(Fixnum)의 최댓값은 1073741823이며, 이 값을 넘으면 Bignum 객체가 됩니다.
10. irb나 pry에서 거대한 출력 피하기
irb에서 작업하다가 아주 큰 배열이나 문자열의 내용이 화면을 가득 채우는 것이 싫다면, 코드 끝에 ;(세미콜론)만 붙여 주면 됩니다.
예시:
require 'rest-client'
RestClient.get('www.rubyguides.com');
;를 빼고 다시 실행해 보면 그 차이를 바로 알 수 있습니다 🙂
11. caller 메서드로 현재 호출 스택 확인하기
코드 예시입니다:
def foo bar end def bar puts caller end foo
실행 결과:
-:3:in 'foo' -:10:in '<main>'
현재 메서드의 이름이 필요하다면 __method__ 또는 __callee__를 사용하세요.
보너스 1. 어떤 값이든 불리언(Boolean)으로 변환하기
!!(1) # true !!(nil) # false
보너스 2. 예약어를 변수 이름처럼 사용하기
def foo (if: nil) binding.local_variable_get(:if) end foo(if: true)
마무리
이번에 소개한 루비 트릭들이 재미있었기를 바랍니다!
친구들에게도 공유해 주시고, 아래 폼에서 블로그를 구독하시면 다음 글을 놓치지 않을 수 있습니다. 🙂