: map, flatMap, fliter 같은 고차 함수가 리스트를 처리하는 강력한 구성 요소이지만 때로 프로그램을 이해하기 힘들 수 있다.
Ex.
scala> case class Person(name: String,
val lara = Person("Lara", false)
" 모든 엄마와 자식의 쌍을 찾고 싶다. " scala> persons filter (p => !p.isMale) flatMap (p =>
" filter 대신 withFilter를 사용하면 중간 데이터 구조를 만들 필요가 없다. " scala> persons withFilter (p => !p.isMale) flatMap (p =>
" for 표현식이 이해가 쉽고 명확하다. " scala> for (p <- persons; if !p.isMale; c <- p.children) |
: 스칼라 컴파일러는 결과를 yield하는 모든 for 표현식을 map, flatMap, withFilter 등의 고차 메소드 호출을 조합한 표현식으로 반환한다. yield가 없는 for 표현식은 withFilter와 foreach만을 사용한다.
23.1 for 표현식
" 일반적인 for 표현식 " for ( seq ) yield expr |
: seq는 제너레이터, 정의, 필터를 나열한 것으로 연속된 구성요소 사이에는 세미콜론을 넣는다. 괄호 대신 중괄호를 사용하면 세미콜론을 생략해도 된다.
for { |
" 제너레이터의 형태 " pat <- expr |
: expr 표현식은 보통 리스트를 반환한다
: pat 패턴은 리스트의 모든 원소를 하나씩 매치해 얻는다. 매치에 성공하면 패턴 안의 변수에는 리스트 원소의 해당 부분에 있는 값이 들어간다. 매치에 실패하도 MatchError를 발생시키지 않고 무시하고 다음 이터레이션을 진행한다.
: x<-expr 처럼 pat가 변수 하나로만 되어 있는 경우가 가장 일반적이고 변수 x는 expr이 반환하는 모든 원소를 단지 이터레이션
" 정의의 형태 " pat = expr |
: pat 패턴을 expr의 값에 바인딩하고 val이 된다.
: x = expr 처럼 변수 하나로 되어 있는 경우가 가장 일반적
" 필터의 형태 " if expr |
: expr은 Boolean 타입의 값이다. expr이 false를 반환하는 값을 이터레이션에서 제외하도록 만든다.
23.2 n 여왕 문제
: N*N 체스판에서 n개의 여왕 말을 서로 잡지 못하게 놓는 문제 (여왕 말은 같은 열, 행, 또는 대각선 상에 있으면 서로를 잡을 수 있다.)
=> N*N 체스판의 왼쪽 위 구석 칸은 (1,1), 오른쪽 아래 칸은 (N,N)
=> 같은 행에 있으면 안 되기 때문에 모든 행마다 여왕을 배치 => 각 행에 차례로 여왕말 배치, 이 때 새로운 행에 놓은 여왕이 이미 체스판에 위치한 다른 여왕들을 잡을 수 있는 위치인지 검사
=> 1 부터 k-1번째 행까지의 여왕말을 잡을 수 없는 k번째 행 여왕말을 어디에 배치할 지 결정하는 문제
=> 문제의 해를 각 여왕의 체스판 위치를 표시하는 좌표 리스트로 표현
=> k번째 행에 있는 여왕의 좌표를 리스트의 맨 앞에, 그 뒤에 k-1번째 행에 있는 여왕을 놓는 식으로 해를 도출
def isSafe(queen: (Int, Int), queens: List[(Int, Int)]) = |
23.3 for 식으로 질의하기
: for 표기법은 기본적으로 데이터베이스 질의 언어의 공통 연산들과 동등하다.
case class Book(title: String, authors: String*)
" 작사의 성이 Gosling인 모든 책의 제목을 찾는다. " scala> for (b <- books; a <- b.authors |
23.4 for 표현식 변환
: 모든 for 표현식은 map, flatMap, withFilter 고차 함수로 표현가능하고 스칼라 컴파일러는 for 표현식을 고차 함수로 변환한다.
< 제너레이터가 하나 밖에 없는 for 표현식의 변환 >
for ( x <- expr1 ) yield expr2 => expr1. map( x => expr2 ) |
< 제너레이터로 시작하고 필터가 하나 있는 for 표현식의 변환 >
for ( x <- expr1 if expr2 ) yield expr3 => for ( x <- expr1 withFilter (x => expr2 ) ) yield expr3 => expr1 withFilter ( x=>expr2 ) map ( x => expr3 )
" 필터 다음에 다른 원소가 있더라도 마찬가지로 변환 " for ( x <- expr1 if expr2; seq ) yield expr3 // seq : 제너레이터, 정의, 필터 => for ( x <- expr1 withFilter (x => expr2 ) ; seq ) yield expr3 |
< 제너레이터 2개로 시작하는 for 표현식의 변환 >
for ( x <- expr1 ; y <- expr2; seq ) yield expr3 => expr1.flatMap( x => for ( y <- expr2; seq ) yield expr3 ) |
: 위 3가지 변환을 통해 제너레이터와 필터로 구성되고 제너레이터가 단순한 변수에 값을 바인딩하는 모든 for 표현식을 변환할 수 있다.
Ex.
for (b1 <- books; b2 <- books if b1 != b2; a1 <- b1.authors; a2 <- b2.authors if a1 == a2) yield a1 books flatMap (b1 => books withFilter (b2 => b1 != b2) flatMap (b2 => b1.authors flatMap (a1 => b2.authors withFilter (a2 => a1 == a2) map (a2 => a1)))) |
< 제너레이터에 있는 패턴의 변환 >
: 제너레이터의 좌항이 단순한 변수가 아니고 패턴 pat라면 변환 방법이 복잡
Ex. 변수의 튜플에 바인딩하는 제너레이터
for ( ( x1, ... , xn) <- expr1 ) yield expr2 => expr1.map { case ( x1, ... , xn) => expr2 } |
Ex. 임의의 패턴 pat라면
for ( pat <- expr1 ) yield expr2 => expr1 withFilter { case pat => true case _ => false } map { case pat => expr2 } |
: 제너레이터가 만들어내는 원소들 중에서 pat와 일치하는 것만 걸러내서 map에 넘긴다.
: 결코 MatchError가 발생하지 않는다.
< 정의 변환 >
for ( x <- expr1; y = expr2; seq) yield expr3 => for ( ( x,y ) <- for ( x<- expr1) yield (x, expr2); seq) yield expr3 |
: expr2가 계산되는 시점은 새로운 x 값을 제너레이터에서 가져올 때 => expr2 안에서 x를 사용한다면 각 이터레이션마다 가져온 값을 반영한다.
: for 표현식에 어떤 정의를 넣을 때 그 정의보다 앞에 있는 제너레이터가 바인딩한 변수를 사용하지 않는 정의를 넣는 것은 바람직하지 않다.
for (x <- 1 to 1000; y = expensiveComputationNotInvolvingX) |
< for 루프 변환 >
: 아무 값도 돌려주지 않고(yield를 사용하지 않고) 부수 효과만 사용하는 for 루프는 foreach만 사용한다.
for ( x <- expr1 ) body => expr1 foreach ( x => body )
for ( x<-expr1; if expr2; y <- expr3 ) body => expr1 withFilter ( x => expr2 ) foreach ( x => expr3 foreach ( y => body )) |
23.5 역방향 적용
: 모든 map, flatMap, filter 호출은 for 표현식으로 표현할 수 있다.
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 } |
23.6 for 일반화
: 스칼라 컴파일러는 for 표현식을 오직 map, flatMap, withFilter 메소드만을 사용해 변환한다.(for 루프는 foreach) 사용자가 정의한 데이터 타입 안에 map, flatMap, withFilter, foreach 메소드를 정의하면 for 표현식을 완벽히 지원할 수 있다.
: 리스트나 배열, 스칼라 표준 라이브러리에는 네 가지 메소드를 정의했기 때문에 for 표현식/루프 사용이 가능하다.
: 네 가지 메소드 중 일부만 정의했다면 for 표현식/루프 기능 중 일부만 사용 가능하다.
- 정확한 규칙
1. map만 정의했다면 제너레이터가 1개만 있는 for 표현식을 사용할 수 있다.
2. map과 flatMap을 함께 정의했다면 제너레이터가 여럿 있는 for 표현식도 사용 가능하다.
3. foreach를 정의했다면 for 루프(제너레이터 개수는 관계없음)를 사용할 수 있다.
4. withFilter를 정의하면 for 표현식 안에서 if로 시작하는 for 필터 표현식을 사용할 수 있다.
- 컴파일러는 for 표현식에서 고차 함수로의 변환을 타입 검사 이전에 수행한다. 그렇기 때문에 최대한의 유연성을 제공할 수 있다. for 표현식을 변환해 나온 결과만을 타입 검사하면 되기 때문이다. 스칼라에는 for 표현식 자체에 대한 타입 검사 규칙은 없다. 또한 map, flatMap, withFilter, foreach가 특정 타입 시그니처를 만족하도록 요구하지도 않는다.
- for 표현식을 변환할 때 사용하는 고차 메소드들의 가장 일반적인 의도를 담는 전형적인 설정이 있다.
abstract class C[A] { // 어떤 종류의 컬렉션을 나타내는 파라미터화된 클래스 |
: withFilter 호출 뒤에는 항상 map, flatMap, foreach 중 하나가 호출되어 withFilter가 만든 객체는 그 즉시 다른 메소드로 인해 사용된다. => withFilter와 map/flatMap/foreach 사이에 withFilter로 생긴 새로운 컬렉션에 대한 작업이 없다. => 새로운 컬렉션을 만들지 말고 withFilter로 반환된 원소를 즉시 map/flatMap/foreach에 적용한다.
=> 이를 위한 표준적인 방법은 withFilter가 C를 반환하는 대신 나중에 처리할 수 있는 필터링된 원소를 기억하는 래퍼 객체를 만들어 반환하는 것이다.
* 모나드
- 타입을 인자로 받는 타입이다. (M[T])
- 임의 타입의 값을 받아서 그 타입을 인자로 받은 모나드 타입의 값을 반환하는 함수가 있어야 한다. ( T 타입의 값을 받아서 M[T] 타입의 값을 반환하는 함수 - unit operator )
- T 타입의 변수를 받아 M[U] 타입의 모나드를 반환하는 함수를 인자로 받아 M[U] 타입의 값을 반환하는 함수가 있어야 한다.( bind operator ) => 모나드에서 다른 모나드로 진행할 수 있다.
: 타입을 포함하는 컬렉션이 대표적이고 map, flatMap, withFilter을 통해 상태나 I/O, 백트래킹, 트랙잭션을 수행한다. => for 표현식의 개념이 단순히 컬렉션에 대해 이터레이션하는 것보다 더 일반적인 개념
: map, flatMap, withFilter에다가 원소 값을 하나 받아 모나드를 만들어 주는 unit operator을 추가하면 모든 모나드를 표현할 수 있다. => unit operator은 인스턴스 생성자/ 팩토리 메소드 => map, flatMap, withFilter는 모나드 개념을 객체지향에 적용한 것
'스칼라' 카테고리의 다른 글
스칼라 25장 스칼라 컬렉션의 아키텍처(Programming in Scala, 3rd) (0) | 2019.06.24 |
---|---|
24장 컬렉션 자세히 들여다보기(1) - Traversable, Iterable, Seq, 집합, 맵(Programming in Scala, 3rd) (0) | 2019.06.24 |
스칼라 22장 리스트 구현(Programming in Scala, 3rd) (0) | 2019.06.23 |
스칼라 21장 암시적 변환과 암시적 파라미터(Programming in Scala, 3rd) (0) | 2019.06.23 |
스칼라 20장 추상 멤버(Programming in Scala, 3rd) (0) | 2019.06.07 |