-
[스칼라] 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 }