레이블과 선택적 인자

레이블과 선택적 인자 (Labelled and Optional Arguments)

함수의 매개변수에 이름과 기본값을 줄 수 있어요. 이것은 널리 '레이블(label)'이라고 불려요. 이번 튜토리얼에서는 레이블을 사용하는 방법을 배워볼게요. 코드는 UTop에서 작성돼요. 이 문서에서 레이블이 붙지 않은 매개변수를 위치 매개변수(positional parameters) 라고 부를게요.

출처: OCaml 공식 문서 - Labelled and Optional Arguments

전제 조건 (Prerequisites)

  • 값과 함수 (Values and Functions)

함수 매개변수에 이름과 기본값을 주는 것이 가능해요. 이것은 널리 레이블(labels)이라고 알려져 있어요. 이 튜토리얼에서는 레이블을 사용하는 방법을 배울게요.

이 튜토리얼 전체에서 코드는 UTop로 작성돼요. 이 문서에서 레이블이 붙지 않은 매개변수는 위치 매개변수(positional parameters) 라고 해요.

레이블이 붙은 인자 전달하기 (Passing Labelled Arguments)

표준 라이브러리의 함수 Option.valuedefault라는 레이블이 붙은 매개변수를 가져요.

# Option.value;;
- : 'a option -> default:'a -> 'a = <fun>

레이블이 붙은 인자는 물결표 ~를 사용해 전달하며, 어떤 위치에도 어떤 순서로도 놓을 수 있어요.

# Option.value (Some 10) ~default:42;;
- : int = 10

# Option.value ~default:42 (Some 10);;
- : int = 10

# Option.value ~default:42 None;;
- : int = 42

참고: 파이프 연산자(|>)로 레이블이 붙은 인자를 전달하면 구문 오류가 나요:

# ~default:42 |> Option.value None;;
Error: Syntax error

매개변수에 레이블 붙이기 (Labelling Parameters)

함수 정의에서 매개변수에 이름을 붙이는 방법은 다음과 같아요:

# let rec range ~first:lo ~last:hi =
    if lo > hi then []
    else lo :: range ~first:(lo + 1) ~last:hi;;
val range : first:int -> last:int -> int list = <fun>

range의 매개변수 이름은:

  • 함수 본문 안에서는 평소처럼 lohi
  • 함수를 호출할 때는 firstlast. 이들이 레이블이에요.

range의 사용은 다음과 같아요:

# range ~first:1 ~last:10;;
- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

# range ~last:10 ~first:1;;
- : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

레이블과 매개변수 이름이 같을 때는 더 짧은 문법을 쓸 수 있어요.

# let rec range ~first ~last =
    if first > last then []
    else first :: range ~first:(first + 1) ~last;;
val range : first:int -> last:int -> int list = <fun>

매개변수 정의에서 ~first~first:first와 같아요. 인자 ~last를 전달하는 것은 ~last:last를 전달하는 것과 같아요.

선택적 인자 전달하기 (Passing Optional Arguments)

선택적 인자(optional arguments)는 생략할 수 있어요. 전달할 때는 물결표 ~나 물음표 ?를 사용해야 해요. 어떤 위치에든 어떤 순서로든 놓을 수 있어요.

# let sum ?(init=0) u = List.fold_left ( + ) init u;;
val sum : ?init:int -> int list -> int = <fun>

# sum [0; 1; 2; 3; 4; 5];;
- : int = 15

# sum [0; 1; 2; 3; 4; 5] ~init:100;;
- : int = 115

선택적 인자를 option 타입의 값으로 전달하는 것도 가능해요. 인자를 전달할 때 물음표를 사용하면 돼요.

# sum [0; 1; 2; 3; 4; 5] ?init:(Some 100);;
- : int = 115

# sum [0; 1; 2; 3; 4; 5] ?init:None;;
- : int = 15

기본값이 있는 선택적 매개변수 정의하기 (Defining Optional Parameters With Default Values)

이전 섹션에서 우리는 선택적 매개변수가 있는 함수를 정의했지만, 어떻게 동작하는지 설명하지는 않았어요. 이 함수의 다른 변형을 살펴볼게요:

# let sum ?init:(x=0) u = List.fold_left ( + ) x u;;
val sum : ?init:int -> int list -> int = <fun>

동작은 같아요. 하지만 이 경우 ?init:(x = 0)~init이 기본값이 0인 선택적 매개변수라는 뜻이에요. 함수 안에서 매개변수 이름은 x예요.

이전 섹션의 정의는 ?(init = 0)?init:(init = 0)과 같게 만드는 단축 표기를 사용한 거예요.

기본값이 없는 선택적 매개변수 정의하기 (Defining Optional Parameters Without Default Values)

선택적 매개변수는 기본값을 지정하지 않고 선언할 수 있어요.

# let sub ?(pos=0) ?len:len_opt s =
    let default = String.length s - pos in
    let length = Option.value ~default len_opt in
    String.sub s pos length;;
val sub : ?pos:int -> ?len:int -> string -> string = <fun>

여기서 우리는 표준 라이브러리의 String.sub 함수의 변형을 정의하고 있어요.

  • s는 부분 문자열을 추출할 문자열이에요.
  • pos는 부분 문자열의 시작 위치예요. 기본값은 0이에요.
  • len은 부분 문자열의 길이예요. 없으면 기본값은 String.length s - pos예요.

선택적 매개변수에 기본값이 주어지지 않으면, 함수 안에서 그것의 타입은 option이 돼요. 여기서 len은 함수 시그니처에서 ?len:int로 나타나요. 하지만 함수 본문 안에서 len_optint option이에요.

이것은 다음과 같은 사용을 가능하게 해요:

# sub ~len:5 ~pos:2 "immutability";;
- : string = "mutab"

# sub "immutability" ~pos:7 ;;
- : string = "ility"

# sub ~len:2 "immutability";;
- : string = "im"

# sub "immutability";;
- : string = "immutability"

len 매개변수와 레이블 이름에 같은 이름을 사용하는 것도 가능해요.

# let sub ?(pos=0) ?len s =
    let default = String.length s - pos in
    let length = Option.value ~default len in
    String.sub s pos length;;
val sub : ?pos:int -> ?len:int -> string -> string = <fun>

선택적 인자와 부분 적용 (Optional Arguments and Partial Application)

표준 라이브러리의 String.concat 함수(타입은 string -> string list -> string)의 두 가능한 변형을 비교해 볼게요.

첫 번째 버전에서는 선택적 구분자가 마지막에 선언된 매개변수예요.

# let concat_warn ss ?(sep="") = String.concat sep ss;;
Line 1, characters 15-18:
  Warning 16 [unerasable-optional-argument]:
  this optional argument cannot be erased.
val concat_warn : string list -> ?sep:string -> string = <fun>

# concat_warn ~sep:"--" ["foo"; "bar"; "baz"];;
- : string = "foo--bar--baz"

# concat_warn ~sep:"";;
- : string list -> string

# concat_warn ["foo"; "bar"; "baz"];;
- : ?sep:string -> string = <fun>

두 번째 버전에서는 선택적 구분자가 첫 번째로 선언된 매개변수예요.

# let concat ?(sep="") ss = String.concat sep ss;;
val concat : ?sep:string -> string list -> string = <fun>

# concat ["foo"; "bar"; "baz"] ~sep:"--";;
- : string = "foo--bar--baz"

# concat ~sep:"--";;
- : string list -> string = <fun>
t
# concat ["foo"; "bar"; "baz"];;
- : string = "foobarbaz"

두 버전의 유일한 차이는 매개변수를 선언하는 순서예요. 두 함수는 인자 ["foo"; "bar"; "baz"]에만 적용했을 때를 제외하고는 동일하게 동작해요. 그 경우:

  • concat"foobarbaz"를 반환해요. ~sep의 기본값 ""이 전달돼요.
  • concat_warn은 타입 ?sep:string -> string의 부분 적용된 함수를 반환해요. 기본값은 전달되지 않아요.

대부분의 경우 concat이 필요해요. 따라서 함수의 마지막에 선언된 매개변수는 선택적이어서는 안 돼요. 경고는 concat_warnconcat으로 바꾸라고 제안해요. 그것을 무시하면 제공되어야 하는 선택적 매개변수를 가진 함수가 노출되는데, 이는 모순적이에요.

참고: 선택적 매개변수는 컴파일러가 함수가 부분 적용되었는지 아닌지 알기 어렵게 만들어요. 그래서 선택적 매개변수 뒤에는 최소한 하나의 위치 매개변수가 필요해요. 적용 시점에 존재한다면 함수가 완전히 적용된 것이고, 없다면 부분 적용된 것이에요.

파이프 연산자로 레이블이 붙은 인자 전달하기 (Passing Labelled Arguments Using the Pipe Operator)

함수의 레이블이 없는 인자를 첫 번째로 선언하면 함수 타입을 읽기 쉬워지고, 파이프 연산자로 이 인자를 전달하는 것도 막지 않아요.

앞서 정의한 range 함수에 step 매개변수를 추가해 수정해 볼게요.

# let rec range step ~first ~last = if first > last then [] else first :: range step ~first:(first + step) ~last;;
val range : int -> first:int -> last:int -> int list = <fun>

# 3 |> range ~last:10 ~first:1;;
- : int list = [1; 4; 7; 10]

선택적 인자만 가진 함수 (Function with Only Optional Arguments)

함수의 모든 매개변수를 선택적으로 만들어야 한다면, 마지막에 오는 더미 위치 매개변수를 추가해야 해요. () unit 값이 여기에 아주 적합해요. 여기서 그렇게 하고 있어요.

# let hello ?(who="world") () = "hello, " ^ who;;
val hello : ?who:string -> string = <fun>

# hello;;
- : ?who:string -> unit -> string = <fun>

# hello ();;
- : string = "hello, world"

# hello ~who:"sabine";;
- : unit -> string = <fun>

# hello ~who:"sabine" ();;
- : string = "hello, sabine"

# hello () ?who:None;;
- : string = "hello, world"

# hello ?who:(Some "christine") ();;
- : string = "hello, christine"

unit 매개변수가 없으면 optional argument cannot be erased 경고가 발생했을 거예요.

선택적 인자 전달하기 (Forwarding an Optional Argument)

물음표 기호 ?로 선택적 인자를 전달하면 언래핑(unwrapping) 없이 그대로 전달(forwarding)할 수 있어요. 이 예제들은 Defining Optional Parameters Without Default Values 섹션에서 정의한 sub 함수를 다시 사용해요.

# let take ?len s = sub ?len s;;
val take : ?len:int -> string -> string = <fun>

# take "immutability" ~len:2;;
- : string = "im"

# let rtake ?off s = sub ?pos:off s;;
val rtake : ?off:int -> string -> string = <fun>

# rtake "immutability" ~off:7;;
- : string = "ility"

takertake의 정의에서, 함수 sub는 물음표로 전달된 선택적 인자들과 함께 호출돼요.

take에서 선택적 인자는 sub에서와 같은 이름을 가져요. ?len이라고 쓰는 것만으로 언래핑 없이 전달되기에 충분해요.

결론 (Conclusion)

함수는 이름이 붙거나 선택적인 매개변수를 가질 수 있어요. 레이블에 대한 더 많은 예제와 자세한 내용은 레퍼런스 매뉴얼을 참고하세요.

더 알아보기 (Learn more)