오버로딩
오버로딩 (Overloading)
become_older라는, 나이를 얼마나 늘릴지 숫자를 받는 메서드를 정의한다고 해 볼게요.
class Person
getter :age
def initialize(@name : String, @age : Int = 0)
end
def become_older
@age += 1
end
def become_older(years)
@age += years
end
end
john = Person.new "John"
john.age # => 0
john.become_older
john.age # => 1
john.become_older 5
john.age # => 6
즉 같은 이름에 매개변수 개수가 다른 메서드를 여러 개 둘 수 있고, 그것들이 각각 별개의 메서드로 취급돼요. 이걸 메서드 오버로딩(method overloading) 이라고 해요.
출처: Crystal 공식 문서
본문
메서드는 여러 기준으로 오버로드돼요.
- 매개변수의 개수
- 매개변수에 적용된 타입 제한
- 필수 named 매개변수의 이름
- 메서드가 블록을 받는지 여부
예를 들어 네 가지 서로 다른 become_older 메서드를 정의할 수 있어요.
class Person
@age = 0
# Increases age by one
def become_older
@age += 1
end
# Increases age by the given number of years
def become_older(years : Int32)
@age += years
end
# Increases age by the given number of years, as a String
def become_older(years : String)
@age += years.to_i
end
# Yields the current age of this person and increases
# its age by the value returned by the block
def become_older(&)
@age += yield @age
end
end
person = Person.new "John"
person.become_older
person.age # => 1
person.become_older 5
person.age # => 6
person.become_older "12"
person.age # => 18
person.become_older do |current_age|
current_age < 20 ? 10 : 30
end
person.age # => 28
블록을 받는 메서드의 경우, 컴파일러는 yield 표현식이 있기 때문에 그 사실을 알아차려요. 더 명확하게 나타내고 싶다면 마지막에 더미 &block 매개변수를 추가할 수 있어요.
class Person
@age = 0
def become_older(&block)
@age += yield @age
end
end
생성된 문서에서는 더미 &block 메서드가, 직접 쓰든 안 쓰든 항상 나타나요.
매개변수 개수가 같다면, 컴파일러는 제한이 덜 엄격한 메서드를 뒤로 보내도록 정렬하려고 해요.
class Person
@age = 0
# First, this method is defined
def become_older(age)
@age += age
end
# Since "String" is more restrictive than no restriction
# at all, the compiler puts this method before the previous
# one when considering which overload matches.
def become_older(age : String)
@age += age.to_i
end
end
person = Person.new "John"
# Invokes the first definition
person.become_older 20
# Invokes the second definition
person.become_older "12"
하지만 항상 전체 순서(total ordering)가 있는 건 아니라서 컴파일러가 순서를 항상 알아내지는 못해요. 그래서 제한이 덜 엄격한 메서드를 뒤에 두는 편이 항상 더 좋아요.
주의사항 (Caveats)
오버로드 순서가 의도한 대로 되지 않는 알려진 컴파일러 버그들이 있어요. 안타깝게도 이 버그를 고치면, 이 특정하고 의도하지 않은 오버로드 순서에 의존하는 기존 코드가 깨질 수 있어요. 기존 코드를 깨뜨리지 않으려고 노력하기 때문에 고치기 쉽지 않아요.
미리보기 플래그
일부 버그 수정은 이미 컴파일러 플래그 -Dpreview_overload_order(Crystal 1.6.0에서 도입)로 사용할 수 있어요.
새 Crystal 코드를 작성할 때는 이 플래그 사용을 고려해 보세요.
이 플래그는 언젠가 기본으로 켜질 것으로 예상돼요. 그 시점에는 모든 Crystal 코드가 올바른 오버로드 순서를 쓰게 될 것이고, 여전히 잘못된 순서에 의존하는 코드는 전환 기간 동안 옵트아웃 기능 플래그를 쓸 수 있어요.
알려진 버그
-
매개변수가 없는 오버로드가 기본값 있는 오버로드를 덮어써요 (#10231)
def bar(x = true) end def bar end bar 1 # Error: wrong number of arguments for 'bar' (given 1, expected 0)이 문제는
-Dpreview_overload_order로 해결돼요. -
오버로드 순서가 타입 제한에 쓰인 타입의 정의 순서에 의존해요 (#7579, #4897)
class Foo end def foo(a : Bar) 1 end def foo(a : Foo) true end class Bar < Foo end foo(Bar.new) # => true # This should be 1해결 방법으로,
Bar의 선언을def foo보다 앞으로 옮길 수 있어요.
더 알아보기
- blocks and procs: 블록을 받는 메서드의 오버로드
- 타입 제한: 오버로드 판별에 쓰이는 메커니즘