ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스칼라 문법편] CH08 함수 컴비네이터
    레거시/레거시-누구나 쉽게 스칼라+플레이 2019. 1. 30. 22:13
    반응형

    * 이 포스팅은 책 "누구나 쉽게 스칼라 + 플레이 - 고락윤 한빛 미디어"를 읽고 정리한 것입니다.

    CH08 함수 컴비네이터


    이번 장에서 배울 함수 컴비네이터는 Java8의 Stream API와 유사합니다. 스칼라의 컬렉션은 대부분 함수 컴비네이터를 제공하여, 컬렉션을 가공 및 조작을 보다 쉽게 해줍니다. 지금부터 배워봅시다.

    01. map


    먼저 컬렉션 요소마다 변경하고 싶을 경우 map을 사용하면 됩니다. 다음 코드를 살펴보도록 하죠.

    //main
    val l = List(1, 2, 3, 4, 5)
    val n = l.map( i => i * i )
    val m = l.map( i => s"ITEM ${i} ")
    
    println(n)
    println(m)


    n은 원본 l의 각 요소마다 자신을 곱한 요소를 갖는 리스트를 만들었습니다. 또한 m처럼 정수형 요소들을 문자열 요소로 바꾸는 것도 가능하지요. map 은 각 요소를 특정 식에 해당하는 값으로 매핑한다고 생각하시면 됩니다. 또한, 부수 효과가 없는 컴비네이터입니다.

    02. foreach


    때로는 컬렉션을 순회해서 일을 처리하고 싶을 떄가 있습니다. 이때 쓰이는 컴비네이터가 foreach 입니다. 다음 코드를 보시죠.

    //,main 
    val l = List(1, 2, 3, 4, 5)
    l.foreach( i => println(s"ITEM ${i}") )


    이 코드는 각 요소마다 순회하여 ITEM ${요소}를 출력 시킵니다. 다소 권장되지는 않습니다만 이런 식으로도 쓰일 수가 있습니다.

    def getSummation(l: List) = {
        var s = 0
        l.foreach(i => s += i)
        s
    }


    이 경우에, s가 상태를 가지기 때문에 별로 좋은 방식은 아닙니다. 이럴 경우에는 4절에서 쓰이는 foldLeft, foldRight가 보다 권장하는 방식입니다.

    03. filter


    필터는 컬렉션에서 조건을 충족하는 요소들을 걸러낼 때 쓰는 컴비네이터입니다. 주로 map과 같이 자주 쓰이지요. 다음 코드를 보시죠.

    //main
    val l = List(1, 2, 3, 4, 5)
    
    l.filter( i => i % 2 != 0 )
     .foreach( i => println(i))


    이 코드는 1, 2, 3, 4, 5 리스트를 만든 후, 홀수 인 것만을 걸러낸 후 각 요소를 순회하여 출력시키는 코드입니다. 실제로, 출력문을 보면 1 3 5가 한 줄 한 줄 출력되는 것을 볼 수 있습니다. 또한, 자신이 원하는 조건에 충족 시키지 않는 값들을 걸러내고 싶을 때 스칼라는 filter 조건에 부정을 해주어도 좋지만 filterNot이라는 컴비네이터를 제공합니다. 다음과 같이 쓸 수 있죠.

    //main
    val l = List(1, 2, 3, 4, 5)
    
    val odd = l.filter( i => i%2 != 0 ) 
    val even = l.filterNot( i => i%2 != 0 )


    이제 odd, even에는 각각 [1, 3, 5], [2, 4]가 들어가 있게 됩니다. 여기서 비슷한 함수 컴비네이터 partition 도 알아갑시다. filter 는 조건을 충족시키는 요소들만 뽑아서 새로운 리스트를 만듭니다. partition은 조건을 충족하는 리스트와 조건을 충족시키지 않는 리스트로 나눕니다. 즉 바로 위 예제에서 filter, filterNot 을 쓴 부분을 한 줄로 줄일 수 있습니다. 이렇게 말이죠.

    //main
    val l = List(1, 2, 3, 4, 5)
    val (odd, even) = l.partition( i => i%2 != 0 )


    이렇게 하면, 이전과 같이 odd, even 에 맞는 각각의 리스트가 배정됩니다. parition으로 나뉠 때 첫 번째에는 조건을 충족시키는 리스트가, 두 번째에는 조건을 충족시키지 않는 리스트가 들어가 있게 됩니다.

    04. fold


    fold는 한국말로 접다라는 단어인데, 이것은 컬렉션의 각 요소들을 접어서 합치는 컴비네이터입니다. 다른 함수형 API의 reduce와 같습니다. 아까 foreach로 만든 getSummation을 fold 계열의 foldLeft 함수를 이용해 개선시켜보겠습니다.

    def getSummation(l: List[Int]) = l.foldLeft(0)( (x, y) => x+y )


    이전 코드보다 짧고 명료해졌습니다. foldLeft는 첫 번째 커링 인수로 초깃값을 두 번째 커링 인수로 (T, T) => T 형식의 함수를 받습니다. 따라서 초깃값 0부터 시작해서 1, 2, 3, 4, 5를 순서대로 합칩니다. 더 자세히 보자면,

    0  (Init 0)
    1  (0 + 1)
    3  (1 + 2)
    6  (3 + 3)
    10 (6 + 4)
    15 (10 + 5)
    


    다음과 같이 왼쪽부터 계속 누적되어서 순서대로 더해지는 방식입니다. foldLeft와 반대로 오른쪽에서 접는 foldRight도 있으며, 접는 것을 더 개발자가 커스터마이징 할 수 있는 fold 함수가 있습니다.

    05. zip


    zip 함수는 리스트 2개를 합치는 역할을 합니다. 요소 요소마다 튜플 형식으로 합쳐서 새로운 리스트를 만드는 것이죠. 코드로 살펴보시죠.

    //main
    val i = List(1, 2, 3, 4, 5)
    val s = List("A", "B", "C")
    
    val z = i zip s
    z.foreach( t => println(t) )


    이렇게 코드를 작성하면 어떤 것이 출력될까요? 출력 형식은 다음과 같습니다.

    (1, A)
    (2, B)
    (3, C)
    


    zip에서 중요한 점은 짝이 맞지 않으면 버린다는 것입니다. unzip은 이렇게 zipping된 리스트를 다시 쌍을 분리하여 리스트로 만들어줍니다. 코드로 살펴보시죠.

    //위의 z
    val (ui, uz) = z unzip;
    println(ui)
    println(uz)


    이 코드를 실행하면, 출력 결과는 숫자리스트와 문자열 리스트로 나뉜 것을 알 수 있습니다.

    List(1, 2, 3)
    List(A, B, C)
    


    06. find


    find는 주어진 조건에 대해서 맞는 첫번째 요소를 반환합니다. 코드로 살펴보시죠.

    //main
    val i = List(1, 2, 3, 4, 5)
    val s = i.find( e => e <= 2 )
    val n = i.find( e => e == 9 )
    
    println(s, n)


    출력 결과는 다음과 같습니다.

    (Some(1), None)
    


    find는 조건에 충족시키는 첫 요소를 Some으로 래핑하여 값을 가져오고 없으면 None을 가져옵니다. 즉 find의 반환형은 Option[T] 입니다.

    07. drop


    drop은 컬렉션 요소를 해당 인덱스까지 떨궈버리는 함수 컴비네이터입니다. 코드로 살펴보도록 하죠.

    //main
    val l = List(1, 2, 3, 4, 5, 6)
    println( l drop 3 )


    이 코드를 출력해보면 실제로 0번부터해서 3개를 버리게 됩니다. 즉 3번째 인덱스 요소인 4부터 5, 6을 갖는 리스트를 반환합니다. drop에 좀 더 진보적인 함수로 조건으로 drop 시키는 dropWhile이 있습니다. 다만 여기서 주의할 점은 dropWhile은 첫 요소분터 순회해서 조건을 만족시키지 않을 때, drop시키는 함수입니다. 코드로 보시면 이해가 빠를 것입니다.

    //main
    val l = List(5, 3, 2, 1, 4)
    println( l.dropWhile( e => e>=2 ) )
    println( l.dropWhile( e => e<=2 ) )


    이 코드의 결과는 어떻게 될까요? 실제 출력은 다음과 같습니다.

    List(1, 4)
    List(5, 3, 2, 1, 4)
    


    첫 dropWhile은 2까지, e >= 2를 충족시킵니다. 1에서 이 조건을 충족시키지 않는데 이 때 충족했던 요소들을 버립니다. 두 번째 dropWhile 에서는 첫 요소인 5가 e <= 2 에 충족시키지 않으니 아무것도 버리지 않는 것을 확인할 수 있습니다. 즉, dropWhile은 조건을 만족시키지 않는 첫 요소의 이전 요소들을 버리는 함수 컴비네이터입니다.

    08. flatten


    flatten 은 컬렉션을 평평하게 만들어주는 녀석입니다. 예를 들어 2차원 리스트 존재할 때, 1차원 리스트로 만들어 두는 것이죠. 코드로 살펴보도록 하죠.

    //main
    val matrix = List(
        List(1, 2, 3),
        List(4, 5, 6),
        List(7, 8, 9)
    )
    
    val line = matrix flatten;
    println(line)


    이 코드를 출력하면 실제로 1, 2, ..., 9 인 1차원 리스트가 나옵니다. 다른 함수형 API, flatMap과 유사하다고 생각하시면 됩니다.

    이렇게 해서 스칼라의 주요 함수 컴비네이터를 모두 알아보았습니다. 스칼라는 OOP에서 FP로 보다 쉽게 넘어가게 하기 위해서 만들어졌다고 해도 과언이 아닙니다. 그렇기 때문에 스칼라는 불변 속성을 이용하여, 컬렉션과 함수 컴비네이터를 이용하여 데이터를 연산하는 기술을 터득하는게 중요합니다. 어떠신가요? ㅎㅎ 어렵나요;; 계속해서 스칼라에 대해서 저와 함께 공부하고 싶으시다면, 다음 장 기타 문법에서 만나도록 합시다!

Designed by Tistory.