Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

Ruby 변환 메서드 완벽 가이드: to_s, to_i부터 to_str, to_int까지 차이점과 활용법

정수를 다루고 있는데 문자열 메서드(예: 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을 구현한 유일한 클래스들:

  • String
  • NameError::message
  • Warning::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'도 확인해 보세요.