관리 메뉴

HAMA 블로그

스칼라 강좌 (28) - Currying(커링) 본문

Scala

스칼라 강좌 (28) - Currying(커링)

[하마] 이승현 (wowlsh93@gmail.com) 2016. 11. 8. 14:30

* 참고로 커링이 무엇인가요에 대한 대답은 너무 쉽다. 하지만  왜 커링이 그렇게 유용하나요? 에 대한 대답은 아직 못얻었다. 뇌 로는 알겠지만 체득하지 못한 상태.  스칼라를 함수형 파라다임으로 사용을 많이 해봐야 알게 되지 않을까..

스칼라에서의 커링 (currying)


1. 개념 

스칼라 Doc 에서는 이렇게 말합니다.  (http://docs.scala-lang.org/ko/tutorials/tour/currying)

메소드에는 파라미터 목록을 여럿 정의할 수 있다. 파라미터 목록의 수 보다 적은 파라미터로 메소드가 호출되면, 해당 함수는 누락된 파라미터 목록을 인수로 받는 새로운 함수를 만든다.

중요한 포인트를 뽑아내어 쉽게 설명해보면 

매우 쉬운 설명 

 - 메소드라는건 파라미터를 가지고 있지요?  예를들어  add( int x , int y)  이렇게 2개를 가지고 있을때
 - add 라는 메소드를 호출 할 때 인자 2개를 넣어주지 않으면 보통 에러 나잖습니까?
 - 근데 하나만 넣어도 에러가 나지 않는데다가 ~~   즉 add (10)  이렇게 해두 되고 
 - 누락된 파라미터 만을 사용하는 새로운 메소드를 만들어 준 답니다.~ 즉 add(int y) 라는 새 메소드말이죠.
 - add (int y) 라는 새 메소드는 나중에 호출 할 수 있는데 그 때 이전에 넣어 둔 10이 암시적으로 작용됩니다.

 슈도코드로 살짝 보면 

 int add (int x, int y) {
   return x + y;
 }

원래 이런 함수인데
 add (10) 이렇게 호출하면 add(int y) 라는 메소드가 만들어지며 , 10은 새 메소드에 저장되어 있게 됩니다.

int add( int y) {
    return 10 + y;
}

즉 이런 함수가 자동으로 만들어 진 다는 말이지요.
이 함수를 add(5) 호출하면 결과로 이전에 입력된 10을 더하여 15가 리턴됩니다.

2. 스칼라 예제

  1. object CurryTest extends App {
  2. def filter(xs: List[Int], p: Int => Boolean): List[Int] =
  3. if (xs.isEmpty) xs
  4. else if (p(xs.head)) xs.head :: filter(xs.tail, p)
  5. else filter(xs.tail, p)
  6. def modN(n: Int)(x: Int) = ((x % n) == 0)
  7. val nums = List(1, 2, 3, 4, 5, 6, 7, 8)
  8. println(filter(nums, modN(2)))
  9. println(filter(nums, modN(3)))
  10. }

위의 스칼라 예제의 modN(n: Int)(x: Int) 가 커링되는 함수입니다.
보시다시피 modN(n: Int)(x: Int) 는 2개의 파라미터를 넣어야하는 함수입죠..

위에서 설명했다시피 이걸 파라미터 하나만 넣어서 호출 할 수 도 있으며 그렇게 하면 새로운 함수가 만들어진답니다..  즉 modN(2) 이렇게 호출 했더니 에러는 안나고 대신 새로운 함수를 만들어서

def filter(xs: List[Int], p: Int => Boolean)  filter 파라미터 p로 넣어주고 있습니다.

위의 p 파라미터 자리에는  modN(2) 를 호출해서 만들어진 새로운 함수가 들어 간다는 말입니다.
새로운 함수는 이렇게 생겼겠죠?  (이미 2를 n 자리에 넣었다는것을 기억하시구요) 

def modN(x: Int) = ((x % 2) == 0)

3. HOW? 

여기에 하나의 함수를 파라미터로 받아서 , 그 함수를 내부에서 사용하고 2개의 Int 형을 받는 함수를 리턴하는 함수가 있습니다.

def sum(f:Int=>Int):(Int,Int)=>Int={
   def sumF(a:Int,b:Int):Int= 
      if(a>b) 0 else f(a)+ sumF(a+1,b)
   sumF
  }

이런 경우 내부에 함수를 선언하지 않고 , 함수 파라미터 한개와 2개의 Int 형 파라미터 , 즉 3개의 파라미터를 받는 커링 함수를 아래처럼 만들수 도 있습니다.

def sum(f:Int=>Int)(a:Int,b:Int):Int=
  if(a>b) 0 else f(a)+sum(f)(a+1,b)

4. {} 의 사용 

스칼라에서는 함수호출시  ()  대신 {} 중괄호를 사용 할 수 있다.  단 !!! 인재가 하나일 경우 !!

println ("Hello World") 대신해서 println { "Hello World" } 를 사용 할 수 있다는 의미이다.

스칼라에서 이렇게 한 이유는 클라이언트 프로그래머가 중괄호 내부에 함수 리터럴을 사용하도록 하기 위해서이다. 이렇게 작성한 메소드는 호출 시 좀 더 제어 추상화 구문과 비슷해진다.

def withPrintWriter(file: File, op: PrintWriter => Unit ) {

......
}

이런 함수를 호출 할 때는 

withPrintWriter ( new File ("date.txt") , 

writer => writer.println(new java.util.Date) ) 


이렇게 할 수 있는데 , 인자가 2개이기 때문에 { } 를 사용할 수 없다.

하지만 !!!!

def withPrintWriter(file: File) (op: PrintWriter => Unit ) {
val writer = new PrintWriter(file)
try {
op (writer)

} finally {
writer.close()
}
}

이러한 함수라면 {} 를 사용가능하다.

val file = new File("date.txt")
withPrintWriter (file) {
writer => writer.println(new java.util.Date)
}

왜 그러냐면 커링을 통해서 file 을 매개변수로 먼저 넣은 새로운 함수가 생겨졌고, 

그 새로운 함수는  매개변수 하나를 필요로 하는 함수이기 때문이다.


보통 (변수,함수) 매개변수 조합일 때 많이 사용된다. 

def foldLeft[B](z: B)(op: (B, A) => B): B = {
  var result = z
  this foreach (x => result = op(result, x))
  result
}
Vector(1, 2, 3).foldLeft(Vector[Int]()) { (is, i) =>
  is :+ i * i
}

5. 추가 읽을거리 : Partial function 과의 차이 

함수 인자를 생략하는 문법들에는 

- 디폴트파라미터
- implicit 
- partial applied function
- currying 

등 이 있는데 그 중에 헤깔리기 쉬운 currying vs partial function 에 대해 살펴보자. 아래 링크를 읽으심이

http://www.vasinov.com/blog/on-currying-and-partial-function-application/

Comments