ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스칼라] for 표현식 다시 보기
    카테고리 없음 2022. 2. 27. 21:35

    Programming in scala 4th edition chapter 23

     

    1. for 표현식

    기본적으로 for 표현식은 아래와 같은 형태이다.

    for ( seq ) yield expr

    seq: 제너레이터, 정의, 필터에 해당

    • 제너레이터: pat <- expr의 형태로 expr의 원소중 pat 의 패턴에 매치되는 원소들을 반환한다.
    • 정의: pat = expr의 형태
    • 필터: if expr 

     

    2. for 식으로 질의하기

    for 문의 활용을 이해하기 위해 데이터베이스 질의 예제를 보자.

    case class Book(
                   title: String,
                   authors: String*
                   )
    
    val library = List(
      new Book(
        "Structure and Interpretation of Computer Programs",
        "Abelson, Harold", "Sussman, Gerald J."
      ),
      new Book(
        "Principles of Compiler Design",
        "Aho, Alfred", "Ullman, Jeffrey"
      ),
      new Book(
        "Programming in Modula-2",
        "Wirth, Niklaus"
      ),
      new Book(
        "Elements of ML Programming",
        "Ullman, Jeffrey"
      ),
      new Book(
        "The Java Language Specification", "Gosling, James",
        "Joy, Bill", "Steele, Guy", "Bracha, Gilad"
      )
    )

    여기서 작가가 Gosling 으로 시작하는 책을 질의하는 for 문은 아래와 같다.

    for (b <- library; a <- b.authors; if a startsWith "Gosling")
      yield b.title
      // List(The Java Language Specification)

    책 제목에 "Program"을 포함한 책을 질의하는 for 문은 아래와 같다.

    for {
      b <- library
      if b.title contains "Program"
    }
      yield b.title
    //List(Structure and Interpretation of Computer Programs, Programming in Modula-2, Elements of ML Programming)

    최소 두 권 이상의 책을 쓴 작가를 모두 찾는건 아래와 같다.

    for {
      b1 <- library
      b2 <- library
      a1 <- b1.authors
      a2 <- b2.authors
      if b1 != b2
      if a1 == a2
    }
      yield a1 // List(Ullman, Jeffrey, Ullman, Jeffrey)

    위의 결과는 책의 갯수만큼 작가 이름을 출력하게 된다. 중복을 제거하는 함수이다. 

    def removeDuplicate(xs: List[String]): List[String] = {
      if (xs.isEmpty) Nil
      else xs.head :: removeDuplicate(
        xs.tail filter (x => x != xs.head)
      )
    }
    
    removeDuplicate(result) // List(Ullman, Jeffrey)

     

    3. for 표현식 변환

    모든 for 표현식은 map, flatMap, withFilter 세 가지 고차 함수로 표현할 수 있다.

     

    제너레이터가 하나밖에 없는 for 표현식의 변환

    for (x <- expr_1) yield expr_2
    // 아래와 같이 변환 가능
    expr_1.map(x => expr_2)
    
    // 예시
    for (x <- List(1, 2, 3)) yield x * 10
    
    List(1, 2, 3).map(_ * 10) // 둘 다 List(10, 20, 30)

     

    제너레이터로 시작하고 필터가 하나있는 for 표현식의 반환

    for (x <- expr_1 if expr_2) yield expr_3
    // 아래와 같이 변환 가능
    for (x <- expr_1 withFilter (x => expr_2)) yield expr_3
    // 아래와 같이 변환 가능
    expr_1.withFilter(x => expr_2).map(x => expr_3
    
    // 예시
    for {
      x <- List(-2, -1, 0, 1, 2)
      if x >= 0
    } yield x * 10
    
    List(-2, -1, 0, 1, 2).withFilter(_ >= 0).map(_ * 10) // 둘다 List(0, 10, 20)

     

    제너레이터 2개로 시작하는 for 표현식의 변환

    for (x <- expr_1; y <- expr_2; seq) yield expr_3
    // 아래처럼 변경 가능
    expr_1.flatMap(x = for (y <- expr_2; seq) yield expr_3)
    // flatMap 내부의 for 표현식도 map으로 변경 가능
    
    //예시
    val expr_1: List[List[Int]] = List(List(-2, -1), List(0, 1), List(0, 20), List(1, 2))
    for {
      x <- expr_1
      y <- x
      if y != 0
    }
      yield y
    
    expr_1.flatMap(x => x withFilter (x => x != 0) map (x => x))
    // 위 아래 결과 모두 0을 제외한 List(-2, -1, 1, 20, 1, 2) 임

     

    제너레이터에 있는 패턴의 변환

    제너레이터가 패턴을 반환한다면 아래와 같이 변환할 수 있다. 우선 단순한 경우인 튜플을 반환하는 경우

    for ((x_1, ..., x_n) <- expr_1) yield expr_2
    // 아래와 같이 변환 가능
    expr_1.map { case (x_1, ..., x_n) => expr_2 }
    
    // 예시
    val tuples = List((1, 2), (2, 3), (3, 4))
    
    for((left, right) <- tuples) yield left + right
    tuples.map{ case (left, right) => left + right }
    // for와 map 모두 List(3, 5, 7) 반환

    임의의 패턴을 반환하는 경우

    for (pat <- expr_1) yield expr_2
    // 아래와 같이 변환됨
    expr_1.withFilter {
      case pat => true
      case _ => false
    } map {
      case pat => expr_2
    }

     

    정의 변환

    for (x <- expr_1; y = expr_2; seq) yield expr_3
    // 위의 변환은 y가 굳이 for 표현식 내부에 들어갈 필요가 없다
    y = expr_2
    for(x <- expr_1; seq) yield expr_3 // 같은 형태로 변형하는것을 고려해본다.

     

    for 루프 변환

    부수효과만을 위한 for 루프의 변형은 foreach를 이용한다

    for (x <- expr_1) body
    // 아래와 같이 변환
    expr_1.foreach(x => body)
    
    // 예시
    val numbers = List(1, 2, 3, 4, 5)
    
    for(number <- numbers) println(number) // 1 2 3 4 5 출력
    numbers.foreach(println) // 1 2 3 4 5 출력

     

    4. 역방향 적용

    for 표현식을 map, withFilter, flatMap으로 변형하는것을 반대로도 할 수 있다.

    object Demo {
      def map[A, B](xs: List[A], f: A => B): List[B] = 
        for (x <- xs) yield f(x)
      
      def flatMap[A, B](xs: List[A], f: A => List[B]): List[B] =
        for (x <- xs; y <- f(x)) yield y
        
      def filter[A](xs: List[A], p: A => Boolean): List[A] =
        for (x <- xs if p(x)) yield x
    }

     

    5. for 일반화

    리스트, 배열, range, iterator, stream 등의 클래스에는 map, withFilter, flatMap, foreach가 구현되어있다. 만약 어떤 클래스가 for 표현식과 for 루프를 모두 지원하기를 바란다면 위의 4가지 메서드를 구현해주어야한다.

    • map만 정의했다면 제너레이터 1개만 있는 for 표현식을 사용 가능
    • map과 flatMap을 정의했다면 제너레이터가 여럿 있는 for 표현식 사용 가능
    • foreach를 정의 했다면 for 루프 사용 가능
    • withFilter 정의시 for 표현식 내부에서 if로 필터 가능
    abstract class C[A] {
      def map[B](f: A => B): C[B]
      def flatMap[B](f: A => C[B]): C[B]
      def withFilter(p: A => Boolean): C[A]
      def foreach(b: A => Unit): Unit
    }

    댓글

Designed by Tistory.