NMP-Study / FunctionalProgrammingInScala

2 stars 0 forks source link

[함수형 프로그래밍] Chapter5.엄격성과 나태성 #12

Closed duckcalf closed 4 years ago

poporis commented 5 years ago

순수 함수적 자료구조

List(1,2,3,4).map(_ + 10).filter(_ % 2 == 0).map(_ * 3)

List의 프로그램 추적

List(1,2,3,4).map(_ + 10).filter(_ % 2 == 0).map(_ * 3)
List(11,12,13,14).filter(_ % 2 == 0).map(_ * 3)
List(12, 14).map(_ * 3)
List(36, 42)

획일적인 루프 작성보다 -> map과 filter 같은 고차 함수를 이용하는게 이상적 일련의 변화들을 하나의 패스로 융합( fusion)해서 임시 자료 생성을 피하는 것이 좋다.

비엄격성(non-strictness, 나태성[laziness])

https://scastie.scala-lang.org/taA8fRlZT6O5v4pVYOq1vA

poporis commented 5 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함수

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

https://scastie.scala-lang.org/2poZA2paRsGU9ydr3ViNRg

poporis commented 5 years ago

확장 예제: 게으른 목록

Stream (Lazy List)

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 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)
    }

https://scalafiddle.io/sf/lNfmSu5/0

poporis commented 5 years ago

프로그램 서술과 평가의 분리

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(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()

https://scastie.scala-lang.org/qcQnAyrFTAmVBAxWbnIOkA

poporis commented 5 years ago

무한 스트림과 공재귀

작성한 함수들은 점진적 무한 스트림(infinite stream)에도 사용할 수 있습니다.

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

unFold 함수는 일반적인 Stream 구축 함수, 소위 공재귀(corecursive)함수의 예 재귀 함수는 자료를 소비하지만 공재귀 함수는 자료를 생산

https://scalafiddle.io/sf/7bjR93Y/9