[스칼라] for 표현식 다시 보기
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
}