튜플 함수

튜플 함수 (Tupled Function)

함수의 인자(argument)를 하나의 튜플로 묶어서 다루고 싶을 때가 있어요. TupledFunction 타입 클래스는 임의의 애리티(arity)를 가진 함수와, 그 함수의 모든 인자를 하나의 튜플로 받는 동등한 함수 사이를 추상화하는 방법을 제공해요.

출처: Scala 3 Reference

본문

스칼라에서 함수의 애리티가 22까지로 제한되어 있을 때는 오버로딩으로 모든 함수 타입에 공통된 연산을 일반화하는 게 가능했어요. 하지만 함수와 튜플이 22를 넘는 애리티까지 일반화된 지금은, 오버로딩만으로는 이걸 처리할 수 없게 됐죠. TupledFunction 타입 클래스가 바로 이 문제를 해결해 줘요. 어떤 애리티의 함수든 직접 추상화해서, 그 함수의 모든 인자를 하나의 튜플로 받는 동등한 함수로 바꿔주는 거예요.

이 타입 클래스는 다음과 같이 정의돼 있어요.

/** Type class relating a `FunctionN[..., R]` with an equivalent tupled function `Function1[TupleN[...], R]`
 *
 *  @tparam F a function type
 *  @tparam G a tupled function type (function of arity 1 receiving a tuple as argument)
 */
@implicitNotFound("${F} cannot be tupled as ${G}")
sealed trait TupledFunction[F, G] {
  def tupled(f: F): G
  def untupled(g: G): F
}

컴파일러는 다음 조건이 모두 맞을 때 TupledFunction[F, G]의 인스턴스를 자동으로 합성해요.

  • F가 애리티 N의 함수 타입이어요.
  • G가 크기 N의 튜플 하나를 받는 함수이고, 그 타입이 F의 인자 타입들과 같아요.
  • F의 반환 타입이 G의 반환 타입과 같아요.
  • FG가 같은 종류의 함수예요. (둘 다 (...) => R이거나 둘 다 (...) ?=> R)
  • FG 중 하나만 인스턴스화했다면, 나머지 하나는 추론돼요.

예시 (Examples)

TupledFunction을 쓰면 Function1.tupled, ..., Function22.tupled 메서드를 임의의 애리티를 가진 함수로 일반화할 수 있어요. 아래는 tupled를 확장 메서드로 정의한 전체 예시예요.

/** Creates a tupled version of this function: instead of N arguments,
 *  it accepts a single [[scala.Tuple]] with N elements as argument.
 *
 *  @tparam F the function type
 *  @tparam Args the tuple type with the same types as the function arguments of F
 *  @tparam R the return type of F
 */
extension [F, Args <: Tuple, R](f: F)
  def tupled(using tf: TupledFunction[F, Args => R]): Args => R = tf.tupled(f)

TupledFunction을 쓰면 Function.untupled도 임의의 애리티를 가진 함수로 일반화할 수 있어요. (전체 예시)

/** Creates an untupled version of this function: instead of a single argument of type [[scala.Tuple]] with N elements,
 *  it accepts N arguments.
 *
 *  This is a generalization of [[scala.Function.untupled]] that work on functions of any arity
 *
 *  @tparam F the function type
 *  @tparam Args the tuple type with the same types as the function arguments of F
 *  @tparam R the return type of F
 */
extension [F, Args <: Tuple, R](f: Args => R)
  def untupled(using tf: TupledFunction[F, Args => R]): F = tf.untupled(f)

TupledFunctionTuple1.composeTuple1.andThen 메서드를 일반화해서, 더 큰 애리티의 함수나 튜플을 반환하는 함수를 합성하는 데도 쓸 수 있어요.

/** Composes two instances of TupledFunction into a new TupledFunction, with this function applied last.
 *
 *  @tparam F a function type
 *  @tparam G a function type
 *  @tparam FArgs the tuple type with the same types as the function arguments of F and return type of G
 *  @tparam GArgs the tuple type with the same types as the function arguments of G
 *  @tparam R the return type of F
 */
extension [F, G, FArgs <: Tuple, GArgs <: Tuple, R](f: F)
  def compose(g: G)(using tg: TupledFunction[G, GArgs => FArgs], tf: TupledFunction[F, FArgs => R]): GArgs => R = {
  (x: GArgs) => tf.tupled(f)(tg.tupled(g)(x))
}