정수를 다루고 있는데 문자열 메서드(예: gsub)를 사용해야 하는 상황이 생기곤 합니다.
이럴 때 어떻게 해야 할까요?
to_s로 객체를 문자열로 변환한 뒤, 필요한 작업을 마치고 to_i로 다시 정수로 되돌리면 됩니다.
예를 들어:
Integer 타입의 1을 String 타입의 "1"로 변환할 수 있습니다.
그다음 새로운 클래스의 메서드들을 사용할 수 있게 되어, 이전에는 불가능했던 작업을 수행할 수 있습니다.
Ruby에서는 모든 객체가 하나의 클래스에 속하며, 모든 클래스는 고유한 메서드 집합을 가진다는 점을 기억하세요.
이 글에서 배울 내용:
- Ruby에서 사용 가능한 변환 메서드의 종류
- 각 메서드 간의 차이점
- 상황에 맞는 올바른 메서드 선택 방법
그럼 시작해 보겠습니다!
짧은 변환 메서드 (to_s, to_i)
첫 번째 그룹의 변환 메서드는 이미 익숙할 것입니다.
대표적인 메서드:
- to_i
- to_s
- to_a
이 메서드들은 현재 객체를 표현하는 특정 클래스의 새로운 객체를 반환합니다.
예를 들어:
(1..10).to_a # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
이 코드의 의미는:
"1..10이라는
Range객체를 해당 범위를 나타내는Array로 변환하겠다."
Ruby는 개발자가 눈치채지 못하는 사이에 이러한 변환 메서드를 암묵적으로 호출하기도 합니다.
문자열 보간(string interpolation)이 대표적인 예입니다:
"#{1}"
눈에 보이지 않지만 이 코드는 1.to_s를 호출합니다.
다음 코드로 직접 확인해 볼 수 있습니다:
module Log
def to_s
puts "to_s called"
super
end
end
class Integer
prepend Log
end
puts "#{1}"
# "to_s called"
참고: Ruby 2.3 이하 버전에서는
Integer대신Fixnum을 사용해야 합니다. 아니면 차라리 Ruby 버전을 업데이트하는 것이 좋겠죠 🙂
이 메서드들은 꽤 관대한 편이며, 예외를 발생시키지 않도록 설계되어 있습니다.
예를 들어:
"aaaaaa".to_i # 0
이 0이 다소 의외로 느껴질 수 있습니다.
하지만 숫자가 전혀 포함되지 않은 문자열에 to_i를 호출하면 바로 이런 결과가 나옵니다.
이 글 뒷부분에서 살펴보겠지만, 이 메서드의 더 엄격한 버전도 존재합니다.
긴 변환 메서드 (to_str, to_int)
자, 그럼:
짧은 변환 메서드가 이미 있는데 왜 to_str, to_int 같은 메서드가 또 필요할까요?
차이점은 무엇일까요?
답은 두 메서드의 '의도(intent)'에 있습니다.
Ruby의 모든 클래스(BasicObject 제외)는 자신을 어떤 형태의 문자열 표현으로 반환하는 to_s 메서드를 구현합니다.
하지만 클래스가 문자열을 반환한다고 해서 그 클래스가 문자열처럼 동작한다는 의미는 아닙니다!
예제를 살펴보겠습니다:
"" + 1 # TypeError: no implicit conversion of Fixnum into String
잘 생각해 보세요:
빈 문자열에 정수를 더하면 무엇이 반환되어야 할까요?
저는 잘 모르겠습니다.
"1"(숫자 1이 담긴 문자열)이라고 말할 수도 있습니다.
하지만 왜 그래야 할까요?
대부분의 사람들이 기대하는 결과가 아닙니다.
문자열과 정수를 더하는 것 자체가 말이 안 되기 때문에 TypeError가 발생하는 것입니다.
설령 그것이 허용된다 해도요.
이건 어때요?
"" + {}
빈 문자열에 빈 해시를 더하는 코드입니다.
이것 역시 말이 안 되죠!
그래서 Ruby는 다형성(Polymorphism)에 좋지 않은 방식인 '객체가 문자열인지' 검사하는 대신, '객체가 문자열처럼 동작하는지'를 확인합니다.
바로 여기서 to_str 메서드가 등장합니다.
Ruby 2.5에서 to_str을 구현한 유일한 클래스들:
StringNameError::messageWarning::buffer
자신만의 변환 메서드 구현하기
어떤 클래스에든 to_str을 구현할 수 있습니다.
그러면 해당 객체를 문자열처럼 사용할 수 있게 됩니다.
예제입니다:
class Cat
def to_str
"meow"
end
end
"" + Cat.new
# "meow"
하지만 클래스가 문자열과 동등하지 않다면 이렇게 구현해서는 안 됩니다.
그런 경우에는 to_s를 사용하세요.
참고로 이것은 + 메서드에만 적용되는 것이 아닙니다.
문자열과 유사한 객체만 의미가 있는 다른 곳에서도 사용됩니다.
예를 들면:
[1,2,3].join(",")
to_int, to_hash 같은 다른 긴 변환 메서드들도 같은 논리를 따릅니다.
바로 이런 이유로 여러 종류의 변환 메서드가 존재하는 것입니다.
변환 래퍼(Conversion Wrapper) 활용하기
아직 변환 메서드가 부족하다고요? 걱정 마세요, 몇 가지 더 소개해 드리겠습니다!
저는 이것들을 "변환 래퍼(Conversion Wrappers)"라고 부르는 것을 좋아합니다.
종류는 다음과 같습니다:
- Array()
- Integer()
- Hash[]
꽤 독특하게 생긴 메서드들이죠!
괄호와 대괄호에 주목하세요...
...이것이 이 메서드들을 일반 클래스 이름과 구분하는 유일한 요소입니다.
이것들의 용도는 무엇일까요?
Array()는 무엇이든 배열로 변환합니다.
몇 가지 예제를 보여 드리겠습니다:
Array(nil)
# []
Array([])
# []
Array(1)
# [1]
Array("")
# [""]
대체 어떤 논리일까요???
사실 이 메서드는 매우 명확한 규칙을 따릅니다:
- 객체가
to_ary또는to_a에 응답하면 해당 메서드를 호출하고 그 결과값을 반환합니다 - 그렇지 않으면 객체를 배열에 넣어 반환합니다
위에서 본 동작이 바로 이 규칙 때문입니다.
배열을 기대하는 메서드를 작성할 때, 다른 타입의 값이 들어와 애플리케이션 전체가 크래시되는 상황을 방지할 수 있습니다. 값을 Array()에 먼저 전달해 배열임을 보장하면 됩니다.
이번엔 Integer()입니다.
몇 가지 특별한 규칙이 있습니다:
- 객체가 문자열이고, 그 내용이 Ruby에서 유효한 숫자 표현에 엄격하게 부합하면 Integer를 반환합니다. 형식이 유효하지 않으면
ArgumentError가 발생합니다. - 객체가 문자열이 아니라면 먼저
to_int, 그다음to_i를 호출하려 시도합니다. - 변환 메서드를 통해서도 유효한
Integer로 변환할 수 없으면TypeError가 발생합니다.
예제:
Integer(1)
# 1
Integer("25")
# 25
Integer("abc123")
# ArgumentError (invalid value for Integer(): "abc123")
Integer([])
# TypeError (can't convert Array into Integer)
유효한 Integer를 다루고 있다는 것을 100% 확실하게 보장하고 싶다면 Integer() 메서드가 유용합니다.
이제 Hash[]입니다.
짝수 개의 요소를 가진 배열을 전달하면 새로운 해시를 얻을 수 있습니다:
Hash[[["a", 1], ["b", 2], ["c", 3]]]
# {"a"=>1, "b"=>2, "c"=>3}
두 배열의 요소를 병합하거나 map 같은 메서드를 사용해 해시를 만들 때 유용합니다.
마무리
지금까지 Ruby의 다양한 변환 메서드, 그 존재 이유, 그리고 사용 방법에 대해 알아보았습니다!
이 글이 도움이 되었기를 바랍니다 🙂
이 글이 마음에 드셨다면 제 책 'Ruby Deep Dive'도 확인해 보세요.