이름 있는 인자와 선택적 인자는 정말 멋지다
요약
본 글은 함수형 언어(OCaml, Gleam) 및 여러 프로그래밍 언어(Swift, Objective-C, Python 등)의 이름 붙은 인자(Named Arguments)와 선택적 인자(Optional Arguments) 기능을 비교 분석합니다. 이러한 기능들은 API 설계 시 매개변수 이름을 명확히 하고 함수의 의도를 파악하기 쉽게 하며, 코드 사용성을 높이는 장점이 있습니다.
핵심 포인트
- 이름 붙은 인자는 API의 의도를 명확하게 전달하여 가독성을 높입니다.
- 선택적 인자는 함수 호출을 유연하게 만들지만, 과도할 경우 복잡성을 야기할 수 있습니다.
- Swift는 내부 매개변수 이름과 외부 레이블을 분리하여 이 기능을 깔끔하게 구현했습니다.
- API 설계 시 명시적인 함수 목록을 제공하는 것이 여러 변형의 함수를 관리하는 것보다 효율적입니다.
OCaml의 이름 붙은 인자와 선택적 인자는 ~와 ? 기호를 사용하는데, 함수형 언어에서는 꽤 독특한 방식임. 직접 써보니 인자 전달과 같은 이름의 변수 활용이 간편해서 상당히 마음에 듦.
예를 들어 let add ~x ?(y = 0) () = x + y로 정의하면, 다른 함수에서 add ~x ?y ()로 인자를 그대로 전달할 수 있음. 현재 범위에 x = 1, y = Some 2가 있다면 print_sum ~x ?y ()로 합계 3을 출력하고, print_sum ~x:4 ()처럼 값을 직접 지정하고 y를 생략하면 4를 출력하게 됨.
Gleam의 명시적인 외부 인자 이름 방식이 마음에 듦. Swift도 같은 방식인 것으로 아는데, 모든 인자에 자동으로 이름을 붙이는 대신 공개할 이름을 명시적으로 선택해야 함.
예를 들어 pub fn replace(in string: String, each value: String, with replacement: String) -> String으로 정의하면, 내부에서는 string, value, replacement를 쓰고 호출할 때는 replace("Wibble", each: "i", with: "o")로 쓸 수 있음. 좋은 API는 우연히 만들어지는 것이 아니라 신중하게 설계되는 만큼, 이름 붙은 인자도 의도적으로 선택해야 한다고 봄.
Objective-C 방식이 마음에 듦. example(named: 1, arg: 2)를 사실상 example_named_arg(1, 2)로 변환하는 방식임.
인자 순서는 고정되지만 Rust의 구문 오류 진단에서 자동 수정을 제안할 수 있음. 가능한 모든 순열을 지원하지 않는 것도 장점으로, 지나치게 많은 설정을 받는 메서드를 만들기 어려워짐. 새로운 객체 타입도 필요 없고, 사용하지 않는 필드나 기본값 필드를 전달하는 부가 비용도 없음. 기존 with_capacity_and_hasher 메서드와 호환되게 만들 수도 있음.
Rust에 이런 기능이 들어간다면 익명 구조체를 인자로 받는 방식이 더 좋겠음. 기존 함수들이 갑자기 이름 붙은 인자로도 호출 가능해지는 것은 원하지 않음. 그렇게 되면 인자 이름만 바꿔도 크레이트 사용자에게 호환성 문제가 생기므로, API 설계자가 명시적으로 선택해야 함.
Python의 키워드 인자와 Ada의 이름 기반 매개변수 연결을 많이 써보면서, 매개변수 이름을 API의 일부로 삼는 것이 꼭 나쁘지는 않다고 생각하게 됨. 명확한 매개변수 이름을 더 고민하게 되고, 함수 시그니처에서 의도를 읽기도 쉬워짐.
라이브러리 API 변경이 더 까다로워지기는 함. 매개변수 이름을 바꾸면 이름을 지정한 호출이 더 이상 컴파일되지 않을 수 있음. 반면 타입은 그대로지만 인자의 의미가 달라지는 변경을 감지할 수 있다는 장점도 있음.
Swift는 내부 매개변수 이름과 외부 인자 레이블을 분리해서 이 부분을 깔끔하게 처리함. 내부 이름을 바꿔도 외부 레이블에는 영향이 없고, 반대도 마찬가지임. Objective-C와 Smalltalk의 전통에 따라 이름 붙은 인자가 기본이지만, 인자별로 이름을 생략하고 위치 기반 인자로 사용하도록 선택할 수 있음.
입력 매개변수 조합마다 따로 있는 여러 함수를 살펴보는 일이 그렇게 나쁜지는 모르겠음. 대신 kwargs 같은 것에 따라 동작이 달라지는 함수 하나가 있어도, 결국 머릿속에서 그 함수의 여러 변형을 따져봐야 함. 문서를 읽고 암묵적인 목록을 직접 만드는 것보다는 라이브러리 작성자가 제시한 명시적인 함수 목록을 살펴보는 편이 훨씬 좋겠음.
이 RFC가 구현되면 정말 반갑겠음. 내 코드에서도 이름 붙은 매개변수와 선택적 매개변수가 자주 필요하고, docs.rs에서 비슷하면서 조금씩 다른 함수 수십 개를 뒤져 원하는 것을 찾는 데 몹시 지침.
반대로 선택적 매개변수를 도입하면, pandas처럼 선택적 매개변수 수십 개가 함수 동작을 완전히 바꾸는 설계가 쏟아질 여지도 생김.
Swift는 이름 붙은 매개변수와 선택적 매개변수를 잘 지원한다고 봄. Swift에서 Rust로 옮기면서 불편했던 부분 중 하나임.
매개변수 이름이 API의 일부가 되는 것을 왜 단점으로 제시하는지 모르겠음. 그것이 바로 이 기능의 목적임. 구현 내부에서는 다른 이름을 쓸 수 있는 문법만 보장하면 되고, Objective-C와 Swift는 이미 이를 지원함.
예를 들어 func scroll(to location: Point)와 func scroll(by delta: Distance)로 정의하면 호출부는 scroll(to: whereIWant), scroll(by: theDistance)가 됨. 이는 분명 scroll_to(...), scroll_by(...)와 동등하지만, 이름 붙은 매개변수의 진가는 인자가 여러 개일 때 드러남. f_x_l_y_doggo(..., ..., ..., ..., ...)보다 f(x: ..., l: ..., y: ..., doggo: ...)처럼 이름을 해당 인자 바로 옆에 두는 것이 유용함. 인자 목록이 길거나 호환되는 타입의 인자가 여럿이면 특히 도움이 됨. 또한 JS나 C++처럼 기본값 인자를 뒤에 몰아놓고, 뒤쪽 기본값 하나만 바꾸려 해도 앞선 인자를 모두 지정해야 하는 문제를 피할 수 있음.
언어의 부족한 기능을 두고 Go에서 흔히 쓰는 “의도된 설계”라는 변명을 하는 것처럼 보이기는 함. 그래도 Rust는 이름 붙은 인자나 선택적 인자가 없어도 괜찮다고 생각함.
온갖 일을 떠맡는 거대 함수가 줄어들고, 선택적 변형을 또 하나 덧붙이기 전에 한 번 더 고민하게 해줌.
AI 자동 생성 콘텐츠
본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기