2018년 4월 23일 월요일

연산형 지역 변수 (Local Computed Variables) | Swift

우연찮게 미디엄 뉴스레터에서 보게 된 글에서 약간의 충격(?)을 받게 된 것이 있는데 바로 제목의 연산형 지역 변수입니다. 말이 잘 이해가 안 될 수도 있는데 영문 표현인 Local Computed Variables 의미를 거의 직역한 것이라서 애매하긴 합니다. 적당한 한국어 표현이 떠오르지 않네요.

하여간 매우 단순한 기능이지만 어떤 면에서 아주 유용하기에 간단하게 소개해 봅니다.

정의

이 지역 연산형 변수를 간단히 정리하기엔 아래와 같은 예제가 더 좋을 것 같습니다.
var onePlusTwo: Int { return 1 + 2 }
연산형 프로터피(Computed Properties)에 대해 알고 있다면 이해하기 쉬울 것입니다. 사실상 모양은 동일하지요. 단순히 클래스나 구조체의 프로퍼티가 아니라 특정 스코프 내의 지역변수로 선언된다는 점만 다릅니다.

지역 연산형 변수는 마치 연상형 프로퍼티 처럼 지역 변수 형태를 띄고 있지만 실제로는 연산형으로 동작하는 변수입니다. 즉 이 변수가 참조 될 때 블럭 내부의 코드가 실행되어서 반환되는 값이 이 변수의 값 처럼 동작하는 것입니다.

제가 약간의 충격을 받았다고 이야기 한 것은 이런 연산형 변수가 지역 변수 형태로도 동작이 가능하다는 사실을 몰랐기 때문입니다. 검색을 해 봐도 Swift의 어느 버전에서 생긴 것인지도 알 수가 없네요. 처음부터 있었을지도 모릅니다. ;-)

물론 비슷한 류의 코드를 기존에도 쓰긴 했었습니다. 바로 클로져를 이용하는 방식이었죠.
let onePlusTwoClosure = { () -> Int in
    return 1 + 2
}
사실상 동일한 동작을 하는 코드입니다.

물론 클로져 방식에 비해 연산형 변수 방식이 구현이 편하기도 하지만, 가독성 측면에서 봐도 차이가 있습니다.
let three = onePlusTwo
let altThree = onePlusTwoClosure()
길이를 비교하지 않는다고 칠 때 어떤 것이 더 가독성이 있는지는 명확합니다. 또한 클로져 방식은 애초에 이것이 변수인지 함수인지 클로져인지 그 호출 규격만 봐서는 알 수가 없다는 특징도 있습니다.

지역 연산형 변수도 동적으로 연산을 하는 변수이기 때문에 당연히 컨텍스트에 따른 동적인 동작이 보장됩니다. 예를 들어 아래 코드를 플레이그라운드에서 돌려봅시다.
var a = 10
var b = 20
var aPlusB: Int { return a + b }

aPlusB     // 30

a = 50

aPlusB     // 70
첫 번째 aPlusB 의 값은 30 이고 두 번째 aPlusB 의 값은 70입니다. 즉, 이 변수가 참조되는 시점에서 a 와 b 변수의 해당 시점의 값을 참조했다는 말이지요.


결론적으로 연산형 지역 변수는 동적으로 계산되는 읽기 전용 변수이며 연산형 프로퍼티(Computed Properties)와 비슷합니다. 그리고 다르게 표현하자면 클로져(Closure)와 비슷합니다.

활용 예제

위에서 예제를 적긴했지만 좀 더 쓸 만한 예제가 필요할 것 같아 만들어 봤습니다. 우선 아래와 같이 동물 타입의 값 구조체를 정의했다고 가정합시다.
struct AnimalType {
    var numberOfLegs = 0
    var isMammal = false
    var isBipedal = false
    var hasIntelligence = false
    var foundInEarth = false
}
팔을 포함한 다리 갯수라던가 포유류인지 이족보행인지 지능이 있는지 등 몇 가지 정보를 가질 수 있는 타입입니다.

이 세상에는 여러 동물이 있습니다.
let animals: [AnimalType] = [AnimalType(numberOfLegs: 4,
                                        isMammal: true,
                                        isBipedal: true,
                                        hasIntelligence: true,
                                        foundInEarth: true),
                             AnimalType(numberOfLegs: 4,
                                        isMammal: true,
                                        isBipedal: false,
                                        hasIntelligence: false,
                                        foundInEarth: true),
                             AnimalType(numberOfLegs: 6,
                                        isMammal: false,
                                        isBipedal: true,
                                        hasIntelligence: false,
                                        foundInEarth: false)]
우리는 어딘가에서 3마리의 동물을 포획해 왔습니다. 뭔가 다양하네요.

어느 이기적이고(?) 돈 많은(?) 사람이 자기 동물원이 텅텅 비었으니 여기서 인기 있을 만한 동물을 동물원에 집어 넣자고 합니다. 동물학대 같지만 씁쓸하게도 자본주의 사회에선 돈이 왕입니다.
var zoo = [AnimalType]()

for animal in animals {
    if animal.numberOfLegs == 4 &&
        animal.isMammal &&
        animal.isBipedal &&
        animal.hasIntelligence &&
        animal.foundInEarth {
        continue
    }
    zoo.append(animal)
}
뭐 어쨌거나 동물원에 동물 틱한 것을 집어 넣었습니다. 외계인이 있는 것 같지만 기분 탓일거예요.

위에서 if 문으로 몇 가지 조건을 검출해서 continue 로 걸러내 동물원에 넣지 않고 버리는 코드가 있습니다. 아무런 주석 없이 이 코드의 의미를 이해하려면 쉽지 않을 것입니다. 눈치 빠른 분이시라면 이해 했을지도 모르겠지만요.

우리는 여기서 연산형 지역 변수를 이용해 리팩토링을 해 볼 수 있습니다.
for animal in animals {
    var isHuman: Bool {
        return animal.numberOfLegs == 4 &&
               animal.isMammal &&
               animal.isBipedal &&
               animal.hasIntelligence &&
               animal.foundInEarth
    }
    
    if isHuman {
        continue
    }
    zoo.append(animal)
}
자 이제 위의 조건의 의미가 이해될 수 있게 되었습니다. Is Human? 즉 사람과 비슷하게 생긴 동물을 검출해 내는 코드였던 겁니다. 사람과 비슷하게 생기면 별로 인기 없을 동물일테니 자기 동물원에 넣지 말라는 이기적인 인간의 판단입니다.

윤리적이지 못한 예제라 죄송합니다. 뭐 하여간 가독성(Readability)을 높이기 위한 활용 예제를 넣고 싶었습니다.

사족

연산형 지역 변수가 있다면 연산형 전역 변수(Global Computed Variables)도 있겠네요? 라는 생각을 하시는 분이 계신가요?

아마도 전역 변수 형태로 선언한다면 동일하게 동작하지 않을까 생각은 합니다만 사실 실험해 보지는 않았습니다. 왜냐하면 전역 변수는 이제는 왠만해선 자제해야 될 코딩 스타일 이니깐요.

여러분은 이미 싱글톤 패턴에 익숙해진 세대라고 생각됩니다. 굳이 전역 변수 스타일은 몰라도 된다고 생각됩니다. 그래서 이 글에서는 전역 선언이 가능한지 조차 시험해 보지 않았습니다.

사실 귀찮아서 안해봤어요 데헷~

[관련글] Swift - 프로퍼티(Properties)
[관련글] 스위프트(Swift) 가이드
[관련링크] Swift Tip: Local Computed Variables

댓글 없음 :