case
case
case는 조금 패턴 매칭처럼 동작하는 제어 표현식이에요. 의미가 조금 다른 if-else-if 체인을 쓸 수 있게 해주고, 더 강력한 구조 몇 가지도 제공해요.
가장 기본적인 형태에서는 값을 다른 값들과 매칭할 수 있어요:
case exp
when value1, value2
do_something
when value3
do_something_else
else
do_another_thing
end
# The above is the same as:
tmp = exp
if value1 === tmp || value2 === tmp
do_something
elsif value3 === tmp
do_something_else
else
do_another_thing
end
표현식을 case의 주제(subject)와 비교할 때 컴파일러는 case 포함 연산자 ===를 사용해요. ===는 Object에 메서드로 정의되어 있고, 서브클래스가 case 문에서 의미 있는 동작을 주기 위해 오버라이드할 수 있어요. 예를 들어 Class는 객체가 그 클래스의 인스턴스일 때를, Regex는 값이 정규식과 매칭될 때를, Range는 값이 그 범위 안에 포함될 때를 case 포함으로 정의해요.
when의 표현식이 타입이라면 is_a?가 사용돼요. 게다가 case 표현식이 변수나 변수 할당이라면 그 변수의 타입이 제한돼요:
case var
when String
# var : String
do_something
when Int32
# var : Int32
do_something_else
else
# here var is neither a String nor an Int32
do_another_thing
end
# The above is the same as:
if var.is_a?(String)
do_something
elsif var.is_a?(Int32)
do_something_else
else
do_another_thing
end
암시적 객체(implicit-object) 문법을 사용하면 when에서 case 표현식에 메서드를 호출할 수 있어요:
case num
when .even?
do_something
when .odd?
do_something_else
end
# The above is the same as:
tmp = num
if tmp.even?
do_something
elsif tmp.odd?
do_something_else
end
when 조건 뒤에 then을 써서 본문을 한 줄에 놓을 수도 있어요.
case exp
when value1, value2 then do_something
when value3 then do_something_else
else do_another_thing
end
마지막으로, case의 값을 생략할 수도 있어요:
case
when cond1, cond2
do_something
when cond3
do_something_else
end
# The above is the same as:
if cond1 || cond2
do_something
elsif cond3
do_something_else
end
이렇게 하면 때로는 읽기에 더 자연스러운 코드가 되기도 해요.
출처: Crystal 공식 문서
본문
튜플 리터럴 (Tuple literal)
case 표현식이 튜플 리터럴이면, when 조건도 튜플 리터럴일 때 몇 가지 의미 차이가 있어요.
튜플 크기가 일치해야 해요 (Tuple size must match)
case {value1, value2}
when {0, 0} # OK, 2 elements
# ...
when {1, 2, 3} # Syntax error: wrong number of tuple elements (given 3, expected 2)
# ...
end
언더스코어 허용 (Underscore allowed)
case {value1, value2}
when {0, _}
# Matches if 0 === value1, no test done against value2
when {_, 0}
# Matches if 0 === value2, no test done against value1
end
암시적 객체 허용 (Implicit-object allowed)
case {value1, value2}
when {.even?, .odd?}
# Matches if value1.even? && value2.odd?
end
타입과 비교하면 is_a? 검사를 수행해요
case {value1, value2}
when {String, Int32}
# Matches if value1.is_a?(String) && value2.is_a?(Int32)
# The type of value1 is known to be a String by the compiler,
# and the type of value2 is known to be an Int32
end
완전한 case (Exhaustive case)
when 대신 in을 사용하면 완전한(exhaustive) case 표현식이 돼요. 완전한 case에서는 필요한 in 조건들을 하나라도 빠뜨리면 컴파일 타임 에러가 나요. 완전한 case에는 when이나 else 절이 들어갈 수 없어요.
컴파일러가 지원하는 in 조건들은 다음과 같아요:
유니온 타입 검사 (Union type checks)
case의 표현식이 유니온 값이라면, 각 유니온 타입을 조건으로 쓸 수 있어요:
# var : (Bool | Char | String)?
case var
in String
# var : String
in Char
# var : Char
in Bool
# var : Bool
in nil # or Nil, but .nil? is not allowed
# var : Nil
end
Bool 값 (Bool values)
case의 표현식이 Bool 값이라면 true와 false 리터럴을 조건으로 쓸 수 있어요:
# var : Bool
case var
in true
do_something
in false
do_something_else
end
Enum 값 (Enum values)
case의 표현식이 flags가 아닌 enum 값이라면, 그 멤버들을 상수 또는 술어 메서드(predicate method)로 조건에 쓸 수 있어요.
enum Foo
X
Y
Z
end
# var : Foo
case var
in Foo::X
# var == Foo::X
in .y?
# var == Foo::Y
in .z? # :z is not allowed
# var == Foo::Z
end
튜플 리터럴 (Tuple literals)
조건들은 case 표현식의 요소들이 만들 수 있는 모든 조합을 빠짐없이 다뤄야 해요:
# value1, value2 : Bool
case {value1, value2}
in {true, _}
# value1 is true, value2 can be true or false
do_something
in {_, false}
# here value1 is false, and value2 is also false
do_something_else
end
# Error: case is not exhaustive.
#
# Missing cases:
# - {false, true}
더 알아보기
case는===(case 포함 연산자)로 값을 매칭하고,when에 타입을 쓰면is_a?검사로 동작해요.when에서.even?같은 암시적 객체 문법으로case표현식에 메서드를 호출할 수 있어요.- 튜플을 매칭할 때는 크기가 일치해야 하고
_와 암시적 객체(그리고 타입 비교)를 쓸 수 있어요. in을 쓰면 완전한(exhaustive) case가 되고, 빠뜨린 조합이 있으면 컴파일 타임 에러가 나요.