커링(currying).. 좋아하세영?

kitty·2022년 2월 1일
5

Functional Programming 의 근사한 기교 중에 하나인 커링을 탐구해보아욤.

우리는 자주 메서드나 함수에서 객체를 리턴하는 것은 이상하게 여기지 않아왔으나, 함수를 리턴하는 것은 그렇게 익숙한 느낌은 아니었나봐요

이제 진정하게 함수를 일급시민이라고 인정하면서 함수를 리턴하기로 해용. 그런데 함수를 '연속적으로' 리턴하는 기교가 있는데.. 일반적으로 이 기교를 커링이라고 부릅니담.

다시말해서 f라는 함수의 리턴값이 g라는 함수이며, g라는 함수는 다시 h라는 함수를 리턴하고.. 마치 객체의 그래핑 탐색을 함수에 적용한 개념이라고 할까욤?

fun sum(a: Int): (Int) -> Int {
	val g = { b: Int -> a + b }
	return g
}

sum(1)(2)

최종값 : 3

가장 기본적인 커링의 형태예욤.

sum 이라는 함수는 a를 파라미터로 받으면, b를 파라미터로 받아 a+b를 계산하는 함수를 리턴합니담.

1)
sum(1) 의 리턴은 함수가 되며 해당 함수는 2라는 인자를 받아 앞서 받은 1과 합해서 3을 최종적으로 리턴하게 되졍.

2)
위의 함수에서 앞서 받은 1을 기억할 수 있는 이유는 함수가 클로저의 성질을 갖고 있기 때문이예요. 전문용어로는 1을 캡쳐한다고 얘기해요.

3)
함수가 클로저의 성질을 가질 수 있는 이유는 함수 역시 객체이기 때문이예요.

그런데 여기에서 연산이 점점 더 길어지면 어떻게 될까영?

sum(1)(2)(3) 을 구현하려면?

이러면 중간에 함수가 하나 더 추가되겠죠? 1과 2를 캡쳐하고 최종연산으로 캡쳐한 1과 2에 마지막으로 받는 3과 연산하는 함수를 리턴해야만 해요.

fun sum(a: Int): (Int) -> (Int) -> Int {
	return { c: Int -> { b: Int -> a + b + c } }
}

sum(1)(2)(3)

최종값 : 6

구현은 했지만.. 함수 리터럴로 리턴하니 너무나 가독성이 엉망이네영.

만일 sum(1)(2)(3)(4) 라면..

 fun sum(a: Int): (Int) -> (Int) -> (Int) -> Int {
 	return { d: Int -> { c: Int -> { b: Int -> a + b + c + d } } }
 }

위와 같이 될거예요. 이러면 중간 중간에 함수를 추가하기가 망설여질 수 있어욤. 지금은 단지 파라미터에 대한 덧셈만 처리하는 함수지만 만일 비즈니스로직을 처리하는 함수들을 커링으로 연결했는데 위와 같은 식이라면?

그렇게 되면 커링은 흥미로운 기법일 수는 있지만 실용적으로 사랑받을 수는 없게될거예요.

그래서 커링을 함수 리터럴이 아니라 arrow 라는 함수형 라이브러리를 이용해서 구현해볼거예요.

실은 코틀린은 자바보다는 훨씬 뛰어난 함수적 표현력을 갖고 있지만, 스칼라나 해스켈에 비해선 경우에 따라 살짝 미흡할 수 있는 표현력과 구조창안력을 갖고 있어요. 그점을 보완하고 커버해주는 아이가 arrow 예욤.

https://arrow-kt.io

fun sum(a: Int): (Int) -> (Int) -> Int {
  val f = { b: Int -> a + b }
  val g = { c: Int -> f.andThen { v -> v + c  } }

  return g
}

arrow 를 설치하면 코틀린의 함수형을 확장해서 다양한 고차함수를 심게 되어욤. '함수형(함수타입)에 대한 연산의 확장' -> 이점이 arrow의 위력이졍. 여기선 andThen 이라는 함수를 사용했어영.

커링을 두번할 때, 첫번째 커링을 f 라는 함수로 미리 만들어두고 두번째 커링에서 g 함수를 만드는데.. g가 리턴하는 값을 f에 대한 합성함수로 나타낼 수 있는 점이 핵심이예염.

이와 같은 구조는 커링을 3번하게 되면 다음과 같이 표현할 수 있게 되어욤.

fun sum(a: Int): (Int) -> (Int) -> (Int) -> Int {
	val f = { b: Int -> a + b }
	val g = { c: Int -> f.andThen { v -> v + c  } }
	val h = { d: Int -> g.andThen { f -> f.andThen { v -> v + d } }}

	return h
}

힘들게 함수 리터럴로 나타낼때보다 합성함수의 기호적 표현식을 이용해서 훨씬 함수스럽게 정돈된 형식으로 꾸밀 수 있게 되었어요.

kotlin + arrow 로 '연속적인 커링'도 우아하게 구현할 수 있다는 점을 포스팅 해보았어욤.

profile
금손 꿈나무

0개의 댓글