[75%] Optional과 non-Optional 타입 간 연산자(operator)를 사용할 수 있는 원리

J.Noma·2021년 10월 12일
0

Swift : 가벼운 주제

목록 보기
7/9

Swift에서 Optional 타입과 non-Optional 타입 간 비교연산자(==, != 등)를 사용할 수 있다는걸 알았다

이번 포스팅에서는 Swift에서 연산자라는게 구체적으로 어떻게 정의되길래 이런게 가능한지 알아보자


연산자를 정의하는 문법

예제로 확인하는게 가장 빠를 것 같다

prefix operator +++
infix operator ===
postfix opertaor ---

struct Vector2D {
    var x = 0
    var y = 0
    // prefix ( +++A )
    static prefix func +++ (vector: inout Vector2D) -> Vector2D {
        vector.x += 1
        vector.y += 1
        return vector
    }
    
    // infix ( A === B )
    static func === (left: Vector2D), right: Vector2D) -> Vector2D {
        return Vector2D(x: left.x + right.x, y: left.y + right.y)
    }
    
    // postfix ( A--- )
    static postfix func --- (vector: Vector2D) -> Vector2D {
        vector.x -= 1
        vector.y -= 1
        return vector
    }
}

요약하면,
1. 먼저 global로 operator 등록을 해준다
2. postfix 혹은 prefix를 붙인 타입 메소드(static)로 구현한다
(infix인 경우 생략 가능하다)


그럼 "non-Optional == Optional"구문의 경우, 비교연산자는 어느 타입에 정의된 것을 따를까?

사실 infix 연산자의 경우, left-hand든 right-hand든 어느쪽 타입에서나 정의할 수 있고 심지어 메소드가 아닌 일반 함수로 정의해도 된다

즉, 어디에서 정의하든 global로 동작한다
(문서에 나와있는건 아니고, 실험해보니 그렇더라)

그리고, 특정 타입의 정의부 내에 타입 메소드로 구현할 때는 left든 right든 해당 타입이 하나라도 있어야 컴파일 에러가 발생하지 않는다

증거로, 아래와 같은 코드가 동작함을 확인했다

struct ABC {
    let k = 3
    static func == (left: ABC, right: ABC?) -> String {
        return "left-hand is Optional"
    }
    static func == (left: ABC?, right: ABC) -> String {
        return "right-hand is Optional"
    }
}

func == (left: ABC?, right: ABC?) -> String {
    return "both-hands are Optional"
}


let temp: ABC = ABC()
let optTemp: ABC? = ABC()

print(temp == optTemp) // right-hand is Optional
print(optTemp == temp) // left-hand is Optional
print(optTemp == optTemp) // both-hands are Optional

Swift 표준 라이브러리는 어떻게 구현해놨는지 확인해보자

Swift 표준 라이브러리에 Optional 타입 비교연산자를 어떻게 구현해놨는지 살펴보자

extension Optional : Equatable where Wrapped : Equatable {
    ...
    
    @inlinable public static func == (lhs: Wrapped?, rhs: Wrapped?) -> Bool
    
    ...
}

음..? 양쪽 모두 Optional인 경우밖에 없는데 어떻게 Optional과 non-Optional을 비교할 수 있는걸까

몇 가지 실험해본 결과,
만약 Optional과 non-Optional 간 연산자를 사용하려는데
해당 left, right 타입 조합의 연산자가 따로 정의되어 있지 않더라도
non-Optional을 Swift에서 자동으로 Wrapping하는 것처럼 보인다

또한, 이런 주석을 발견할 수 있었다


그럼 각 타입 조합별로 연산자를 전부 정의해주면 어떤게 실행될까?

참고로, 여기서 타입 조합들이란 아래 경우를 말한다

  • left: Optional / right: Optional
  • left: Optional / right: non-Optional
  • left: non-Optional / right: Optional
  • left: non-Optional / right: non-Optional

알아보려는 것은,
Optional과 non-Optional 간 infix 연산자를 사용할 때
1) Wrapping부터하고, left/right가 모두 Optional인 연산자 정의를 수행하는지
2) 해당 조합으로 정의된 연산자가 있는지부터 확인하고, 없을 때만 Wrapping하는지

예제로 어느 쪽이 맞는지 확인해보았다

struct ABC {
    let k = 3
    static func == (left: ABC, right: ABC?) -> String {
        return "left-hand is Optional"
    }
    static func == (left: ABC?, right: ABC) -> String {
        return "right-hand is Optional"
    }
}

func == (left: ABC?, right: ABC?) -> String {
    return "both-hands are Optional"
}

let temp: ABC = ABC()
let optTemp: ABC? = ABC()

print(temp == optTemp) // right-hand is Optional
print(optTemp == temp) // left-hand is Optional
print(optTemp == optTemp) // both-hands are Optional

결론적으로, 2번이 맞았다
해당 조합으로 정의된 연산자가 있는지부터 확인하고
없다면 non-Optional 타입을 Wrapping하여
양쪽 모두 Optional로 정의된 연산자를 수행한다

쐐기를 박기 위해, Optional+Optional 타입 조합 연산자만 정의해서 결과를 보자

struct ABC {
    let k = 3
}

func == (left: ABC?, right: ABC?) -> String {
    return "both-hands are Optional"
}

let temp: ABC = ABC()
let optTemp: ABC? = ABC()

print(temp == optTemp) // both-hands are Optional
print(optTemp == temp) // both-hands are Optional
print(optTemp == optTemp) // both-hands are Optional

모두 "both-hands are Optional"라고 출력되는 것으로 보아
Swift가 자동으로 wrapping 했다는 사실을 알 수 있다


오늘의 결론

Optional과 non-Optional 간 infix 연산자를 사용할 경우

1) 먼저, 동일한 조합으로 만들어진 연산자 정의가 있는지 찾아보고, 있다면 그걸 수행한다
2) 그렇지 않고, 오직 Optional / Optional 조합만 정의되어 있다면 Swift가 자동으로 non-Optional 타입을 Wrapping한다

profile
노션으로 이사갑니다 https://tungsten-run-778.notion.site/Study-Archive-98e51c3793684d428070695d5722d1fe

0개의 댓글