9장 control abstraction - codeport/scala GitHub Wiki

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)

9.3 Currying

  • 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

9.5 By-name parameters

  • 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

9.6 Conclusion

  • currying과 by-name parameter는 control abstraction 구현에 도움을 준다.
⚠️ **GitHub.com Fallback** ⚠️