Closed duckcalf closed 4 years ago
비엄격한 함수
엄격한 함수
def square(x: Double): Double = x * x
square(41.0 + 1.0) // success
square(sys.error("failure")) // failure
부울 함수 &&와 ||의 단축 평가는 엄격하지 않다.
false && { println("!!"); true }
// 아무것도 출력되지 않음
// 조건이 false가 되면 && 뒤의 조건이 평가되지 않는다.
true || { println("!!"); true }
// 역시 아무것도 출력되지 않음
// 앞의 조건이 true이면 || 뒤의 조건이 평가되지 않는다.
var result = if (input.isEmpty) sys.error("empty input") else input
비엄격 if함수
def if2[A](cond: Boolean, onTrue: () => A, onFalse: () => A): A =
if (cond) onTrue() else onFalse()
if2(a < 22), () => println("a"), () => print("b"))
() => A -> ()를 생략해서 표현할 수 있다.
def if2[A](cond: Boolean, onTrue: => A, onFalse: => A): A =
if (cond) onTrue else onFalse
보통의 함수 호출 구문을 이용하면 성크안의 표현식을 알아서 감싸준다.
if2(false, sys.error("fail"), 3)
def maybeTwice(b: Boolean, i: => Int) = if(b) i+i else 0
val x = maybeTwice(true, { println("hi"); 1+41 })
hi
hi
84
def maybeTwice2(b:Boolean, i: => Int) = {
lazy val j = i
if (b) j+j else 0
}
val y = maybeTwice2(true, { println("hi"); 1+41 })
hi
84
Stream (Lazy List)
Stream in package scala is deprecated (since 2.13.0): Use LazyList instead of Stream https://scastie.scala-lang.org/qcQnAyrFTAmVBAxWbnIOkA
Stream의 간단한 정의
sealed trait Stream[+A]
case object Empty extends Stream[Nothing]
case class Cons[+A](h: () => A, t: () => Stream[A]) extends Stream[A]
object Stream {
def cons[A](hd: => A, tl: => Stream[A]): Stream[A] = {
lazy val head = hd
lazy val tail = tl
Cons(() => head, () => tail)
}
def empty[A]: Stream[A] = Empty
def apply[A](as: A*): Stream[A] =
if (as.isEmpty) empty
else cons(as.head, apply(as.tail: _*))
}
def headOption: Option[A] = this match {
case Empty => None
case Cons(h, t) => Some(h())
}
// expensive(x)가 두 번 계산
val x = Cons(() => expensive(x), tl)
val h1 = x.headOption
val h2 = x.headOption
똑똑한(smart) 생성자
메모화(memoization)
def cons[A](hd: => A, tl: Stream[A]): Stream[A] = {
lazy val head = hd
lazy val tail = tl
Cons(() => head, () => tail)
}
def apply[A](as: A*): Stream[A] =
if (as.isEmpty) empty
else cons(as.head, apply(as.tail: _*))
https://scastie.scala-lang.org/iX6c2lpPQxGsQYAbn9htpw
Stream을 List로 변환
def toList: List[A] = {
def go(s: Stream[A], acc:List[A]): List[A] = s match {
case Cons(h, t) => go(t(), h() :: acc)
case _ => acc
}
go(this, List()).reverse
}
Stream의 처음 n개의 요소를 돌려주는 함수 take(n)
def take(n: Int): Stream[A] = this match {
case Cons(h, t) if n > 1 => cons(h(), t().take(n - 1))
case Cons(h, _) if n == 1 => cons(h(), empty)
case _ => empty
}
Stream의 처음 n개의 요소를 건너뛴 스트림을 돌려주는 drop(n)
def drop(n: Int): Stream[A] = this match {
case Empty => empty
case Cons(h, t) if n > 1 => t().drop(n-1)
case Cons(h, t) if n == 1 => t()
}
Stream에서 주어진 술어를 만족하는 선행 요소들을 모두 돌려주는 함수 takeWhile
def takeWhile(p: A => Boolean): Stream[A] = this match {
case Empty => empty
case Cons(h, t) => if (p(h())) cons(h(), t().takeWhile(p)) else t().takeWhile(p)
}
def exists(p: A => Boolean): Boolean = this match {
case Cons(h, t) => p(h()) || t().exists(p)
case _ => false
}
재귀를 foldRight의 형태로 구현
def foldRight[B](z: => B)(f: (A, => B) => B) : B =
this match {
case Cons(h, t) => f(h(), t().foldRight(z)(f))
case _ => z
}
def exists(p: A => Boolean): Boolean =
foldRight(false)((a,b)=> p(a) || b)
점진적(incremental)
Stream의 모든 요소가 주어진 술어를 만족하는지 점검하는 forAll 함수
def forAll(p: A => Boolean): Boolean = this match {
case Cons(h, t) => if(p(h())) t().forAll(p) else false
case _ => true
}
def forAll(f: A => Boolean): Boolean =
foldRight(true)((a,b) => f(a) && b)
Stream에 대한 프로그램 추적
Stream(1,2,3,4).map(_ + 10).filter(_ % 2 == 0).toList
// Stream(1,2,3,4)는 Stream.apply 메소드에 의해 cons 메소드를 통해 다음과 같이 변환된다.
cons(1, Stream(2,3,4)).map(_ + 10).filter(_ % 2 == 0).toList
(1 :: Stream(2,3,4)).map(_ + 10).filter(_ % 2 == 0).toList
(11 :: Stream(2,3,4).map(_ + 10)).filter(_ % 2 == 0).toList
cons(11, Stream(2,3,4).map(_ + 10)).filter(_ % 2 == 0).toList
//그 다음 cons의 head와 tail에 filter를 적용하게 된다.
cons(11, Stream(2,3,4).map(_ + 10)).filter(_ % 2 == 0).toList
(11 :: Stream(2,3,4).map(_ + 10)).filter(_ % 2 == 0)).toList
Stream(2,3,4).map(_ + 10).filter(_ % 2 == 0).toList
//마찬가지로 Stream(2,3,4)에 대해서도 cons와 map과 filter를 차례로 수행한다.
cons(12, Stream(3,4).map(_ + 10)).filter(_ % 2 == 0).toList()
(12 :: Stream(3,4).map(_ + 10)).filter(_ % 2 == 0).toList()
12 :: Stream(3,4).map(_ + 10).filter(_ % 2 == 0).toList()
12 :: cons(3, Stream(4)).map(_ + 10).filter(_ % 2 == 0).toList()
12 :: cons(13, Stream(4).map(_ + 10)).filter(_ % 2 == 0).toList()
12 :: Stream(4).map(_ + 10).filter(_ % 2 == 0).toList()
12 :: Stream(14).filter(_ % 2 == 0).toList()
12 :: 14 :: Stream().map(_ + 10).filter(_ % 2 == 0).toList()
12 :: 14 :: List()
작성한 함수들은 점진적 무한 스트림(infinite stream)에도 사용할 수 있습니다.
val ones: Stream[Int] = Stream.cons(1, ones)
ones.take(5).toList ones.exists(_ % 2 != 0)
ones는 무한,
작성한 함수들은 요구된 출력을 산출하는데 필요한 만큼만 스트림을 조사
```scala
ones.map(_ + 1).exists(_ % 2 == 0)
ones.taskWhile(_ == 1)
ones.forAll(_ != 1)
세 경우 모두 결과가 즉시 나온다. 단 결코 종료되지 않거나 스택에 안전하지 않은 표현식이 만들어지기 쉬우니 조심해야 한다.
ones.forAll(_ == 1)
주어진 값의 무한 Stream을 돌려주는 함수 constant
def constant[A](a: A): Stream[A] = {
cons(a, constant(a))
}
n에서 시작해서 n +1, n+2 등으로 이어지는 무한 정수 스트림
def from(n: Int): Stream[Int] = {
cons(n, from(n+1))
}
주어진 값의 무한 Stream을 돌려주는 함수 constant
def constant[A](a: A): Stream[A] = {
cons(a, constant(a))
}
n에서 시작해서 n +1, n+2 등으로 이어지는 무한 정수 스트림
def from(n: Int): Stream[Int] = {
cons(n, from(n+1))
}
좀 더 일반화된 스트림 구축 함수 unfold
def unfold[A, S](z: S)(f: S => Option[(A, S)]): Stream[A] =
f(z) match {
case Some((h,s)) => cons(h, unfold(s)(f))
case None => empty
}
Option은 Steam이 종료되는 시점(있는경우)을 지정
unFold 함수는 일반적인 Stream 구축 함수, 소위 공재귀(corecursive)함수의 예 재귀 함수는 자료를 소비하지만 공재귀 함수는 자료를 생산
순수 함수적 자료구조
List의 프로그램 추적
비엄격성(non-strictness, 나태성[laziness])
https://scastie.scala-lang.org/taA8fRlZT6O5v4pVYOq1vA