Swift에서 함수 호출 표현식(여러 구문 형태를 포함하며, 함수 타입에 인자 목록을 적용하는 것)은 현재 이중적인 성격을 가지고 있다. 다음 예시를 보자:
func foo(a : Int, b : Int) {}
이 함수를 호출할 때는 일반적인 구문 형태를 사용해 각 매개변수에 인자를 전달할 수 있다:
foo(42, b : 17)
또는 잘 알려지지 않은 기능을 활용해 전체 인자 목록을 단일 값(튜플 타입)으로 전달할 수도 있다:
let x = (1, b: 2)
foo(x)
이 제안서는 후자의 형태, 즉 “튜플 스플랫(tuple splat)” 형태를 제거할 것을 권장한다. 이 기능은 순수히 편의를 위한 기능이며, 매개변수를 수동으로 전달하는 것 이상의 표현력을 제공하지 않는다.
Swift-evolution 스레드: Proposal: Remove implicit tuple splat behavior from function applications
이 동작은 귀엽고, 다른 함수형 언어에서도 사용되며 몇 가지 장점이 있다. 하지만 구문 형태와 관련된 몇 가지 주요 단점도 존재한다.
foo(x)
호출은 컴파일러와 코드를 유지보수하는 사람 모두에게 foo
의 오버로드된 버전을 호출하는 것처럼 보인다. 이 기능의 존재를 모르면 매우 혼란스럽다.현재 구현은 우리가 원하는 튜플 스플랫(tuple splat) 동작 방식과 다르게 작동한다. 예를 들어, 다음과 같이 foo
를 호출할 수 있어야 한다고 볼 수 있다:
func bar() -> (Int, Int) { ... }
foo(bar())
… 하지만 튜플 라벨이 일치해야 하므로 이는 허용되지 않는다. 대신 다음과 같이 작성해야 한다:
func bar() -> (Int, b: Int) { … }
foo(bar())
이 기능은 실제로 사용하기 매우 어렵다. 많은 파라미터를 _
로 처리해야 하고(네이밍 컨벤션을 위반), 수동으로 섞어야 하며(이 기능의 편의성을 무효화), 함수의 결과에 파라미터 라벨을 추가해야 한다(호출자와 피호출자 사이에 이상한 연결을 초래).
근본적인 문제는 두 가지 형태의 함수 적용에 정확히 같은 구문을 사용한다는 점이다. 두 형태를 구분했다면(아래 “고려한 대안”에서 다룰 예정) 이러한 문제 중 일부는 사라졌을 것이다.
역사적 관점에서, 튜플 스플랫 형태의 함수 적용은 Swift 설계 초기(아마도 2010년, 혹은 2011년)로 거슬러 올라간다. 당시 모든 함수 적용은 단일 값을 함수 타입에 적용하는 형태였다. 여러 이유(inout, 기본 인자, 가변 인자, 라벨 등)로 이 모델을 완전히 포기했지만, 튜플 스플랫 동작을 재평가하지는 않았다.
만약 이 기능이 이미 존재하지 않았다면, Swift 3에 추가하지 않았을 것이다(적어도 현재 형태로는).
제안하는 해결책은 간단하다. Swift 3 컴파일러에서 이 기능을 제거해야 한다. 이상적으로는 Swift 2.2 컴파일러에서 기능을 폐기(deprecate)하고, Swift 3에서는 완전히 제거하는 것이 좋다. 하지만 Swift 2.2에 폐기 기능을 추가할 시간이 없다면, Swift 3에서 바로 제거해도 문제가 없다고 생각한다. (물론 fixit과 마이그레이션 도움은 제공해야 한다.)
이 기능의 흥미로운 점은 일부 사용자들이 이 기능을 매우 좋아한다는 것이다. 하지만 자세히 물어보면 실제로 코드에서 널리 사용하지 않거나, 사용하더라도 명명 규칙을 왜곡하며 사용하고 있다는 것을 인정한다. 이는 긍정적인 기여로 보이지 않는다. 오히려 ‘영리한’ 기능일 뿐, 실용적이지 않다.
참고: 이 제안은 튜플을 함수의 값으로 전달하는 기능을 제거하는 것을 의미하지 않는다. 예를 들어, 다음과 같은 코드는 여전히 완벽하게 유효하다:
func f1(a : (Int, Int)) { ... }
let x = (1, 2)
f1(x)
제네릭을 사용하는 경우도 마찬가지다:
func f2<T>(a : T) -> T { ... }
let x = (1, 2)
f2(x)
이 제안이 영향을 미치는 유일한 경우는 단일 튜플 인자가 컴파일러에 의해 여러 개의 선언된 매개변수로 확장되는 경우다.
설계는 간단하다. Swift 3 시점에서는 지금까지와 마찬가지로 이러한 표현식을 파싱하고 타입 체크를 진행한다. 하지만 튜플 스플랫 형태일 경우 오류와 함께 수정 힌트를 제공한다. 마이그레이터는 다른 경우와 마찬가지로 이 수정 힌트를 자동으로 적용한다.
이 기능을 사용하는 모든 코드는 기존의 전통적인 형태로 변경해야 한다. 위 예제의 경우, 아래와 같은 코드를:
foo(x)
다음과 같은 형태로 다시 작성해야 한다:
foo(x.0, x.b)
만약 “x”가 복잡한 표현식이라면, 임시 변수를 도입해야 한다. 우리는 컴파일러의 자동 수정 기능이 간단한 경우를 직접 처리할 수 있다고 믿으며, 이 확장 기능이 널리 사용되지 않는다고 판단한다.
이 기능의 주요 문제점은 제대로 고려되지 않고 구현되었다는 점이다. 오래된 기능이기 때문에 그냥 유지되며 겨우 버티고 있는 상황이다. 따라서 적절한 기능을 설계해 이를 지원하는 것이 대안이 될 수 있다. 일반 함수 적용과의 암묵성과 문법적 모호성이 문제이므로, 이를 명시적으로 표현할 수 있는 문법 형태를 도입하는 것이 해결책이다. 예를 들어 다음과 같은 방식이 문제를 해결할 수 있다:
foo(*x) // 진지한 문법 제안은 아님
하지만 이 기능을 실제로 설계하는 것은 Swift 3의 핵심 목표와는 거리가 있는 상당한 노력이 필요하다:
이 개념을 심각하게 고려하고 싶다면, 이 제안 이후에 후속 제안으로 진행하는 것이 좋다. 좋은 설계가 나온다면 그 장점을 바탕으로 평가할 수 있다.
마지막 대안은 이 기능을 컴파일러에 그대로 남겨두는 것이다. 하지만 이는 “영원히” 복잡성을 감수하거나 Swift 4 시점에 코드를 깨뜨리는 것을 의미한다. 이미 Swift 3 시점에 마이그레이션이 필요하다는 것을 알고 있으므로, 이 문제를 Swift 3 시점에 해결하는 것이 더 바람직하다.