참고로 이 글은 Swift 4.0 을 기준으로 쓰여 졌습니다.
Closed Range
ClosedRange 라는 타입은 가장 기본적인 범위 타입으로 시작 값과 끝 값이 명확한 범위를 나타내는 타입입니다. 보통은 이 타입 이름을 그대로 쓰지는 않고 아래처럼 범위 오퍼레이터를 이용해서 값을 생성합니다.1...10 // 1 부터 10 까지 1..<10 // 1 부터 9 까지1...10 은 1 이상 10 이하의 범위라는 의미이고, 1..<10 은 1이상 10 미만 범위를 의미합니다.
위의 표현식은 실제로는 ClosedRange 라는 타입으로 생성됩니다.
ClosedRange 타입은 범위 표현 이라는 목적에 맞게 여러 기능이 제공됩니다. 아래는 그 예시입니다.
(1...10).lowerBound // 1 (1...10).upperBound // 10 (1...10).count // 10 (1...10).isEmpty // false굳이 설명이 필요할까 싶을 정도로 이름만 봐도 기능을 알 수 있는 프로퍼티 들입니다. 그런데 솔직히 isEmpty 는 쓸 일이 있을지는 좀 의문이긴 하네요.
count 라는 이름 때문에 배열(Array)와 비슷한 것으로 생각 할 수도 있는데, Range 는 말 그대로 범위를 다루지 해당 범위 내의 여러 아이템의 실체는 존재하지 않습니다. 그저 최소 최대 값 만이 존재하는 타입이지요.
그런데 배열과 비슷하게 범위에도 contains() 메소드가 존재합니다. 동작은 조금 다르지만 의미는 비슷하지요. 예를 들어 특정 값(value)이 있다고 칩시다.
let value = 5이 값이 1 이상 10 미만인지 판단하려면
1 <= value <= 10이런 식으로 코드를 쓰고 싶겠지만 이런 식의 코드는 에러가 나니 [...] 보통은 아래 처럼 쓸 것입니다.
1 <= value && value <= 10솔직히 보기에 좀 안좋은 것 같은데, 이를 아래처럼 범위 표현식을 이용해 좀 다르게 표현 할 수도 있습니다.
(1...10).contains(value) // true사람에 따라 다를 수도 있겠지만, 원래 목적에 의한다면 전 이 방식이 더 읽기가 편하다고 느낍니다. 왜냐하면 논리비교식은 꺽쇠의 방향을 얼핏 반대로 오해할 수도 있거든요. 하지만 범위표현식에서는 이런 착각이 발생할 가능성은 훨신 적다고 보기 때문입니다.
참고로 contains() 메소드 대신 ~= 오퍼레이터를 이용 할 수도 있습니다.
1...10 ~= 5 // true위의 contains() 를 쓰는 것과 동일한 코드입니다. 결과적으로 조금 더 표현이 단순해 지는데, 개인적으로는 생판 모르는 상황에선 오해할 수도 있는 코드 일 수도 있기에 추천하지는 않습니다. 물론 알고 있다면 편하게 쓸 수 있긴 하겠지만요.
앞서 count 프로퍼티에 대한 이야기를 하면서 배열 같다는 이야기를 했는데, 배열 처럼 이터레이션을 하는 특수한(?) 방법을 제공합니다.
(1...10).forEach { value in
print(value)
}
forEach 라는 메소드를 통해 클로저로 이터레이션을 구현한 예제입니다. 위 코드는 아래와 동일한 역활을 한다고 볼 수 있습니다.
for value in 1...10 {
print(value)
}
위 예제 중 위의 것은 함수형 프로그래밍의 모습에 가까워 보이고 아래쪽은 전통적인 for 이터레이션이라는 차이가 있겠지요.위의 인터레이션이 가능한 것은 이 범위 타입이 갯수를 샐 수 있는 타입이라 가능합니다. 이 표현을 영어로 Countable Closed Range 라고 쓸 수 있을 것 같은데 실제로 이 타입이 존재하는 것은 아닙니다. 대신 ClosedRange<Int> 타입의 경우 기본적으로 countable 즉 갯수를 샐 수 있는 것으로 자동으로 판단하는 것 같습니다.
왜 countable 이야기를 하냐 하면 아래와 같은 실수 범위도 생각 해 볼 수 있기 때문입니다.
1.0...10.0위 범위는 ClosedRange<Double> 이라는 타입으로 생성됩니다. 그런데 이 값은 .count 프로퍼티나 .forEach 메소드를 이용 할 수가 없습니다. 아예 해당 멤버가 없다는 컴파일 오류가 발생합니다.
생각해 보면 당연합니다. 정수형의 경우 최소단위가 1 이기 때문에 A라는 숫자의 다음 숫자는 A + 1 이라는 식으로 정의가 가능합니다. 하지만 Double 같은 실수 타입에는 최소단위라는 개념이 없습니다. 따라서 범위 내부에 어떠한 값들이 얼마나 있는지 정의 한다는 건 불가능한 이야기지요.
범위 타입은 앞서 본 것 처럼 제너릭(Generics)이기 때문에 여러 가지 타입으로 정의가 가능합니다. 아래 처럼 Date 타입으로 범위를 만들 수도 있습니다.
let now = Date() let future = now.addingTimeInterval(3600) let period = now...future이렇게 만들어진 기간 범위를 이용해 날짜가 해당 범위에 포함되는지 간단하게 체크 할 수 있습니다.
period.contains(Date().addingTimeInterval(10))개인적으로 날짜 비교에서 영어 표현에 익숙하지 않아서 곤욕스러울 때가 있는데 범위를 이용하는 방식이 훨신 이해가 잘 되는 것 같습니다.
범위에 쓸 수 있는 타입은 사실
Comparable 을 구현한 타입이면 전부 쓸 수 있습니다. 예를 들어 문자열로 범위를 만들수도 있고, 커스텀 클래스나 구조체가 Comparable 을 따르기만 하면 그것으로도 범위를 만들 수 있습니다.Partial Range
Partial Range 는 특수한 범위 타입으로 시작이나 끝이 생략된 형태의 범위를 의미합니다. 닫힌 범위(Closed Range) 가 있었으니 이에 대치되는 열린 범위(Opened Range) 라는 이름일거라 생각했다면 실패했네요. :-P하여간 이 시작값이나 끝 값이 생략된 범위(Partial Range)는 아래와 같은 식으로 표현합니다.
...10 // PartialRangeThrough<Int> 10... // CountablePartialRangeFrom<Int> ..<10 // PartialRangeUpTo<Int>세 가지를 나열했는데, Partial Range 타입도 여러 종류가 있고 여기서는 이 중 세 가지를 꼽아 본 것입니다. 보시다시피 앞의 Closed Range 의 한 쪽이 사라진 형태입니다.
각각의 의미는 코드만 봐도 대체로 이해 가능하실 겁니다. ...10 은 10 이하 라는 범위이고, 10... 은 10 이상 이라는 범위, 그리고 마지막은 10 미만 이라는 범위입니다.
이 타입도 contains() 같은 메소드가 제공됩니다.
(10...).contains(15) // true (10...).contains(5) // false그런데 이 경우라면 굳이 범위식 보다는 그냥 단순 논리식이 편한 것 같습니다.
15 >= 10 // true 5 >= 10 // false꺽쇠 방향을 착각하면 오해가 생기겠지만, 이 Partial Range 자체도 어느 방향에 값이 있냐에 따라 착각이 생길 수 있어서 뭐가 더 낫냐 라는 판단은 보류 하겠습니다. -_-;
시작값(lower bound)이나 끝값(upper bound)이 없으니 이터레이션(forEach)이나 갯수(count) 같은건 못 쓰겠네 라고 생각 할 수 있는데, 여기서 10... 은 Countable 이름이 붙어 있다는 점에 주의해서 봅시다. 이 녀석만은 count 나 forEach 를 왠지 사용 할 수 있을 것 같습니다. 실제로도 underestimatedCount 라는 count 라는 비슷한(?) 프로퍼티가 제공되고, forEach 도 예외처리가 필요한 것을 제외하곤 역시 사용이 가능합니다.
왜 굳이 10... 만 countable 로 표기되는지는 명확하지 않습니다. 기본적인 컴파일러 동작으로 정수형이면서 최소값이 정해져 있으면 제한적으로 동작이 가능하다는 식으로 정의가 되어 있는 것 같습니다.
마무리
개인적으로 범위 표현을 좀 좋아하지 않았었습니다. 왜냐하면 모호한 표기 때문에 어쩔 수 없이 괄호를 써야 하는 점이 싫었기 때문이지요. 예를 들어 앞서 예제로 썼던 코드를 봅시다.(1...10).forEach { ... }
이 코드에서는 괄호를 안쓰면 컴파일 에러가 닙니다. 괄호를 써야 하는 것이 현실이라는 말이지요.물론 범위 자체를 다른 변수에 넣어서 쓰면 되기 하지만 일시적으로 한번만 쓰는 경우가 많아서 낭비하는 기분이 들기도 했구요.
그런데 이 글을 쓰게 되면서, 특히 Date 타입을 범위에 쓰는 예제를 알게 된 이후로 범위에 대한 선호가 약간은 달라질 것 같습니다.
글을 쓸다는 것은 자기 자신에게 공부도 되기에 참 좋은 학습 방법 같습니다. :-)
[관련글] 스위프트(Swift) 가이드
0 comments:
댓글 쓰기