카테고리 없음

[스칼라] for 표현식 다시 보기

Parkjuida 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
}