콘텐츠로 이동

2. Functions in Scala

Functions in Scala

참고: https://www.baeldung.com/scala/functions

개요

  • 부분 적용 함수 (partially-applied functions)
  • 커링 (function currying)
  • 부분 함수 (partial functions)
  • 함수 전반

함수

  • 함수는 일급 시민
    • 함수가 매개변수로 전달, 리턴 가능
    • 변수에 할당할 수 있음
    • 그냥 값 처럼 타입을 지정할 수 있음
  • def
    • 매번 평가
    • 메서드라고 부름
    • 객체나 클래스 내에 정의해야 함.
    • 클래스 인스턴스에서 implicit 참조가 있음
    • 값이 아니며, 타입이 없음
  • val
    • 한번 평가 후 계속 씀
    • function value
    • FunctionN 의 타입 (N : 0 ~ 22)
      • 해당 trait 에 andThen, compose 등의 메서드가 정의되어 있음. (https://www.scala-lang.org/api/2.12.0/scala/Function1.html|)
        val getNameLengthVal: String => Int = name => name.length
        val multiplyByTwoVal: Int => Int = num => num * 2
        
        getNameLengthVal.andThen(multiplyByTwoVal) // 컴파일 잘 된다. 
        
        def getNameLengthDef(name: String): Int = name.length
        def multiplyByTwoDef(num: Int): Int = num * 2
        
        getNameLengthDef.andThen(multiplyByTwoDef)  // 컴파일 안 돼!
        
        // 메서드를 Function Value로 변환하려면 이렇게
        val getNameLengthFnValue = getNameLengthDef _
        val multiplyByTwoFnValue = multiplyByTwoVal _
        
        getNameLengthFnValue.andThen(multiplyByTwoFnValue) // 컴파일 잘 된다.
        

부분 적용 함수 (Partially-Applied Functions)

  • 아규먼트가 부분적으로 적용됨
    • 일반 함수보다 구체적인 함수를 만들 수 있음. 반복적인 코드 피할 수 있음
  • 함수 적용 시, 모든 파라미터에 대해 아규먼트를 넘기지 않고, 몇 개만 넘기고 나머지 빈칸
  • 이러면 스칼라는 남은 파라미터는 순서대로 넣어주면 되는 새로운 함수를 리턴
  • 부분 적용 함수는 항상 새로운 함수 반환해!
  • 원본 함수는 정말 아규먼트가 fully applied 된 경우에만 평가된다.
이렇게 하면 반복적인 코드가 개선이 된다.
def createUrl(protocol: String, domain : String) : String = {
    s"$protocol$domain"
}

val baeldung = createUrl("https://","www.baeldung.com")
val facebook = createUrl("https://","www.facebook.com")
val twitter = createUrl("https://","www.twitter.com")
val google = createUrl("https://","www.google.com")

def createUrl(protocol: String, domain : String) : String = {
    s"$protocol$domain"
}

val withHttpsProtocol: String => String = createUrl("https://", _: String)
val withHttpProtocol: String => String = createUrl("http://", _: String)
val withFtpProtocol: String => String = createUrl("ftp://", _: String)

val baeldung = withHttpsProtocol("www.baeldung.com")
val facebook = withHttpsProtocol("www.facebook.com")
val twitter = withHttpsProtocol("www.twitter.com")
val google = withHttpsProtocol("www.google.com")
  • 많은 보일러 플레이트 함수들을 개선할 수 있음!

함수 커링

  • 커링된 함수는 여러개의 아규먼트 그룹을 하나씩 받도록 함
    • 커링된 함수는 첫번째 아규먼트 그룹을 받아, 그 다음 아규먼트 그룹을 받는 함수를 리턴.
    • 부분 적용 함수와 비슷. (하지만 기술적으로 다름)
  • 함수 커링은...
    • 함수 (A, B, C, D)를 (A => (B => (C => D)))
  • 근데 이거 굳이 왜 함?

    1. Future를 보면, apply 메서드가 커링 함수를 씀

      • 두번째 파라미터 그룹이 implicit execution context 를 받아서 넘김!
        val executionContext = ExecutionContext.fromExecutorService(Executors.newCachedThreadPool())
        
        val future = Future {
          Thread.sleep(1000)
          2
        }(executionContext)
        
    2. 제네릴 쓸 때 유용

      • 컴파일러는 다른 매개변수 그룹에 있어야 추론하여 컴파일 할 수 있음
      • 타입 추론은 매개변수 그룹 왼 -> 오로 흐름.
        def withListItems[T](list : List[T],f : T => Unit) : Unit = {
           list match { 
             case Nil => () 
             case h :: t => 
               f(h)
               withListItems(t,f) 
           } 
         } 
        withListItems(List(1,2,3,4), number => println(number + 2)) // does not compile
        
        def withListItems[T](list : List[T])(f : T => Unit) : Unit = {
          list match {
            case Nil => ()
            case h :: t =>
              f(h)
              withListItems(t)(f)
          }
        }
        
        withListItems(List(1,2,3,4))(number => println(number + 2)) // compiles
        

부분 함수

  • Partially applied function 은 입력 값의 하위 집합에 대해서만 작동하는 함수.
  • trait PartialFunction 이 있음
    val isWorkingAge: PartialFunction[Int, String] = new PartialFunction[Int, String] {
      override def isDefinedAt(x: Int): Boolean = if (x > 18) true else false
    
      override def apply(v1: Int): String = {
        if (isDefinedAt(v1)) {
          "WORK!"
        } else {
          "NO WORK!"
        }
      }
    }
    
    // 이렇게 사용 가능
    val isWorkingAge : PartialFunction[Int,String] = {
      case age if age >= 18 && age <= 60 => s"You are $age years old and within working age"
      case other => s"You are $other years old and not within working age"
    }
    
  • isDefinedAt 어디갔나 하겠지만, 처리 못하는 케이스에 대해서 간주하면 구현이 되나봄
  • orElse, andThen 등 스칼라에서는 많이 쓰임