더 나은 for 컴프리헨션

더 나은 for 컴프리헨션 (Better Fors)

Scala 3.8부터, 또는 Scala 3.7에서 -preview 모드로 쓰면 for 컴프리헨션(for-comprehension)의 사용성이 크게 개선돼요.

출처: Scala 3 Reference

본문

사용자에게 가장 크게 보이는 변화는 for 컴프리헨션을 별칭(alias)으로 시작할 수 있게 된 거예요. 덕분에 이전에는 컴파일되지 않던 다음 코드가 이제 유효해져요:

for
  as = List(1, 2, 3)
  bs = List(4, 5, 6)
  a <- as
  b <- bs
yield a + b

이 코드의 desugaring(설탕 제거)은 별칭을 val로 도입했을 때와 동일해요:

val as = List(1, 2, 3)
val bs = List(4, 5, 6)
for
  a <- as
  b <- bs
yield a + b

추가로, 이 확장은 for 컴프리헨션이 desugaring되는 방식을 바꿔요. 이제 desugaring이 더 직관적으로 이루어지고, 불필요한 메서드 호출 일부를 피할 수 있어서 생성되는 코드가 더 효율적일 수 있어요. desugaring에는 두 가지 주요 변화가 있어요.

  • 순수 별칭에 대한 더 단순한 desugaring: 별칭 뒤에 가드(guard)가 오지 않으면 desugaring이 단순해져요. 마지막 생성자(generator)와 별칭을 튜플로 감쌀 필요가 없고, 대신 별칭을 다음 생성자와 함께 블록 안의 지역 변수로 그냥 도입해요. 이전의 desugaring은:
for {
  a <- doSth(arg)
  b = a
} yield a + b

이렇게 desugar돼요:

doSth(arg).map { a =>
  val b = a
  (a, b)
}.map { case (a, b) =>
  a + b
}

새로운 desugaring은:

doSth(arg).map { a =>
  val b = a
  a + b
}

이 변화는 별칭 뒤에 가드가 오지 않을 때 desugaring을 더 직관적으로 만들고, 불필요한 map 호출을 피할 수 있게 해 줘요.

  • 중복 map 호출 회피: for 컴프리헨션의 결과가 마지막 생성자 패턴과 같은 표현식일 때, desugaring이 불필요한 map 호출을 피해요. 단, 마지막 패턴과 결과의 동일성은 구문적으로 확인할 수 있어야 해서, 변수이거나 변수들의 튜플이어야 해요. 본문이 ()(Unit 상수)를 반환하는 상수 함수(constant function)라면 map을 떨어뜨리는 특별한 경우도 있어요. 현재의 desugaring은:
for {
  a <- List(1, 2, 3)
} yield a

이렇게 desugar돼요:

List(1, 2, 3).map(a => a)

새로운 desugaring은:

List(1, 2, 3)