[11강] 코드 중복 줄이기와 Currying

2015. 4. 24. 15:01Programming/Scala

파일의 마지막 이름이 입력된 문자와 일치하는지를 찾는 함수, 파일 이름에 해당 문자열이 포함되어 있는지를 찾는 함수가 아래와 같이 정의되어 있다고 가정하자.


def fileEnding(query : String) = {

    for (file <- files; if file.getName.endsWith(query))

        yield file

}


def fileContaining(query: String) = {

    for ( file <- files; if file.getName.contains(query))

        yield file

}


딱 보기에도 비슷한 구조로 되어 있다. 이 두 함수를 합칠 수는 없을까??


스칼라에서는 함수를 값으로 인식하기 때문에 매개변수에 각각의 기능을 하는 함수를 직접 넘기면 어떨까??


def filesMatching(query:String, matcher: (String, String) => Boolean) = {

    for(file<-files; if matcher(file.getName, query))

        yield file

}


def fileEnding(query:String) = filesMatching(query, (fileName: String, query:String) => fileName.endsWith(query))

def fileContaining(query:String) = filesMatching(query, (fileName: String, query:String) => fileName.contains(query))


filesMatching((fileName:String, query:String) => fileName.endsWith(query)) 함수는 filesMatching 함수에서 인자 타입을 명시하였기 때문에 인자 타입을 생략할 수가 있다.


def fileEnding(query:String) = filesMatching(query, (fileName, query) => fileName.endsWith(query))


또한 fileName과 query에 대해 전달 받을 인자가 명확하기 때문에 '_'로 표시를 생략할 수가 있다.


def fileEnding(query:String) = filesMatching(query, _.endsWith(_))


fileEnding에서 이미 query 파라미터를 받고 있기 때문에 이 부분도 줄여 보자.


def fileEnding(query:String) = fileMatching(_.endsWith(query))


파라미터가 1개 받는 걸로 줄여졌으니 filesMatching 함수도 수정하자.


def filesMatching(matcher: String => Boolean) = {

    for(file<-files; if matcher(file.getName))

        yield file



Currying


Currying은 함수에서 여러개의 파라미터를 받는 것 대신 파라미터 리스트로 받을 수 있게 해주는 것을 말한다.


def sum(x:Int, y:Int) = x+ y             // not currying

def curried_sum(x:Int)(y:Int) = x + y    // currying


이게 과연 무슨 차이가 있는 걸까??? 아래의 예처럼 부분 적용 함수처럼 쓸 수 있다고는 하는데 확실한 Currying의 용도는 아직 잘 모르겠다. 


val onePlus = curried_sum(1)_

onePlus(2)

3


위의 코드를 설명하자면 curried_sum(1)_의 결과로 두 번째 파라미터를 받아야 하는 함수가 리턴되었고 해당 함수가 onePlus에 저장이 되었다. 그리고 onePlus에 파라미터를 입력하면 값이 리턴이 된다.


loan pattern(빌려 주기 패턴)


아래의 코드를 살펴보자.


import java.io.File


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

    val writer = new PrintWriter(file)

    try{

        op(writer)

    }finally{

        writer.close()

    }

}


withPrintWriter(new File("date.txt"), writer=>writer.println(new java.util.Date))


위의 코드를 보면 입력 파라미터에서는 그 형태만 만들어 주고 실제 해당 함수 내에서 파일 객체를 만들고 닫는 것을 확인할 수가 있다.

이러한 패턴을 loan pattern이라고 한다.


추가로 인자가 하나일 경우에는 소괄호가 아니라 중괄호의 사용이 가능하다. (좀 더 프로그램처럼 보인다랄까...)


println { "Hello World" }


하지만 인자가 두 개일 경우에는 중괄호 대신 소괄호를 사용해야 한다.


str.substring(7, 9)


위의 withPrintWriter 함수는 인자가 두 개임에도 불구하고 currying을 사용해서 중괄호로 표현할 수가 있다.


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

-> withPrintWriter(new File("date.txt")) {

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

}


by-name parameter


만약에 중괄호 내에 인자를 전달하지 않는 함수의 경우 어떻게 표현하는 것이 좋을까? (목적은 함수 호출임에도 while 문처럼 보이고 싶은 것임)


def myAssert(predicate: () => Boolean)


myAssert(() => 5>3)


쓰기에 상당히 어색하다. 스칼라에서는 인자가 없는 경우 생략해도 동작이 가능하다.


def myAssert(predicate: => Boolean)


myAssert( 5 > 3)


어?? 왜 저렇게 조건 식을 함수로 넘길까?? 그냥 조건 식으로 넘겨도 되는 거 아닌가??


조건식을 넘기는 것과 함수를 넘기는 것에는 큰 차이가 있다.


만약 해당 조건 식이 3 / 0 과 같이 exception을 유발할 수 있는 경우 조건 식을 넘겼을 경우에는 예외를 발생시킨다. (인자로 넘어 가는 순간 계산이 이루어진다.)


하지만 만약에 함수로 넘기게 된다면 해당 함수 내에서 함수 호출을 하기 전까지는 예외를 보장할 수 있다. (즉, 이 말은 해당 함수 내에서 예외 처리를 신경쓸 수 있다는 말이 된다.)