2015. 4. 24. 15:01ㆍProgramming/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을 유발할 수 있는 경우 조건 식을 넘겼을 경우에는 예외를 발생시킨다. (인자로 넘어 가는 순간 계산이 이루어진다.)
하지만 만약에 함수로 넘기게 된다면 해당 함수 내에서 함수 호출을 하기 전까지는 예외를 보장할 수 있다. (즉, 이 말은 해당 함수 내에서 예외 처리를 신경쓸 수 있다는 말이 된다.)