-
Notifications
You must be signed in to change notification settings - Fork 36
9장 control abstraction
JeongHoon Byun (aka Outsider) edited this page Jun 23, 2013
·
6 revisions
- 함수를 해부해 보면
- 공통부분: 해당 함수를 호출할 때 매번 같은 부분. 즉, 함수의 바디
- 비공통부분: 함수호출마다 달라질 수 있는 부분. _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))
}
- 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
- 일등급 함수를 가진 언어에서는 새로운 제어구조를 만들 수 있다.
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 구현에 도움을 준다.