9.1 Reducing code duplication
함수를 해부해 보면
공통부분: 해당 함수를 호출할 때 매번 같은 부분. 즉, 함수의 바디
비공통부분: 함수호출마다 달라질 수 있는 부분. _function value_를 인자로 받을 경우, 이 _function value_를 비공통부분으로 볼 수 있음.
higher-order function : 함수를 매개변수로 갖는 함수.
다음 예제를 보자.
object FileMatcher {
private def filesHere = (new java.io.File (" ." )).listFiles
def filesEnding (query : String ) =
for (file < - filesHere; if file.getName.endsWith(query))
yield file
def filesContaining (query : String ) =
for (file < - filesHere; if file.getName.contains(query))
yield file
def filesRegex (query : String ) =
for (file < - filesHere; if file.getName.matches(query))
yield file
}
object FileMatcher {
private def filesHere = (new java.io.File (" ." )).listFiles
private def filesMatching (matcher : String => Boolean ) =
for (file < - filesHere; if matcher(file.getName))
yield file
def filesEnding (query : String ) =
filesMatching(_.endsWith(query))
def filesContaining (query : String ) =
filesMatching(_.contains(query))
def filesRegex (query : String ) =
filesMatching(_.matches(query))
}
9.2 Simplifying client code
API, library 등에 _higher order function_을 잘 사용하면 클라이언트 코드를 간결하게 만들 수 있다.
def containsNeg (nums : List [Int ]): Boolean = {
var exists = false
for (num < - nums)
if (num < 0 )
exists = true
exists
}
// 다음처럼 higer order function을 활용할 수 있다
def containsNeg (nums : List [Int ]) = nums.exists(_ < 0 )
currying : 복수의 인자 목록을 갖는 함수. 각 인자 목록은 ( )
로 구분함
def curriedSum (x : Int )(y : Int ) = x + y
curriedSum(1 )(2 )
val onePlus = curriedSum(1 )_ // 여기서 parameter list 전체를 치환하는 뒷부분 _ 앞의 공백은 생략할 수 있다. 앞 부분의 () 때문으로 보임. cf. println_ (X) <- println_ 자체가 유효한 식별자 이므로
scala> def first (x : Int ) = (y : Int ) => x + y
first: (x : Int )(Int ) => Int
scala> val second = first(1 )
second: (Int ) => Int = <function1 >
scala> second(2 )
res6: Int = 3
9.4 Writing new control structures
일등급 함수를 가진 언어에서는 새로운 제어구조를 만들 수 있다.
scala> def twice (op : Double => Double , x : Double ) = op(op(x))
twice: (op : (Double ) => Double , x : Double )Double
scala> twice(_ + 1 , 5 )
res9: Double = 7.0
loan pattern: control abstraction 패턴으로 보임. 리소스를 인자 function에 '꿔 주는' 패턴. ex) 스트림을 열고 닫는 function / 열린 스트림을 전달받아 뭔가를 하는 function
scala에선 하나의 parameter만 받을 땐 () 를 { } 로 바꿀 수 있음
println(" Hello, world!" )
println { " Hello, world!" } // 둘이 동일함
따라서 { }
로 뭔가 원래 문법틱하게 꾸미려면 single parameter만 받도록 구성해야 함. -> curring을 쓰면 됨 ==> 왠지 꼼수틱한데? 딱히 아래만 보면 별 차이가 없어보이긴 함.
def a( param1: Int, op: Int => Int ) = { op(param1) }
val b = a(3, _: Int => Int )
scala> b( _ * 2)
res9: Int = 6
scala> b { _ * 2 }
res10: Int = 6
def curr_a( param1: Int)(op: Int => Int ) = { op(param1) }
scala> curr_a(3) {
| _ * 2
| }
res11: Int = 6
scala> curr_a(3) { x => x * 2 }
res12: Int = 6
scala> val curr_b = curr_a(3)_
curr_b: Int => Int => Int = <function1>
scala> curr_b ( _ * 2 )
res13: Int = 6
scala> curr_b { _ * 2 }
res14: Int = 6
by-name parameter : 인자로 넘기는 함수가 인자를 갖지 않을 때 표현식이 어색해지는 문제를 해결해 줌. function 정의 시 ()
를 명시하지 않으면 됨.
def noByName ( a : () => Boolean ) = a()
scala> noByName( () => true ) // 앞의 () => 가 보기싫다
res16: Boolean = true
scala> noByName( true )
<console >: 10 : error : type mismatch ;
found : Boolean (true )
required : () => Boolean
noByName( true )
scala> def byName ( a : => Boolean ) = a()
<console >: 8 : error : Boolean does not take parameters
def byName ( a : => Boolean ) = a()
^
scala> def byName ( a : => Boolean ) = a
byName: (a : => Boolean )Boolean
scala> byName( true )
res18: Boolean = true
scala> def noByName ( a : () => Boolean ) = a // a만 도로 return하는 꼴.
noByName: (a : () => Boolean )() => Boolean
scala> noByName( true )
<console >: 10 : error : type mismatch ;
found : Boolean (true )
required : () => Boolean
noByName( true )
^
scala> noByName( () => true )
res24: () => Boolean = <function0 >
scala> noByName( () => true )()
res25: Boolean = true
참고
by name parameter는 function을 넘기기 때문에 나중에 evalution되는 이점을 활용할 수 있음.
currying과 by-name parameter는 control abstraction 구현에 도움을 준다.