zio/zio-prelude

Functional Effect 의 다양한 합성(composition)

wefree 2021. 10. 10. 22:49

문제

아래와 같이 A, B, C, D 를 정의할 때, 다양하게 합성해 보자.

  def A: Option[String] = {
    println("A")
    Some("A")
  }

  def B: Option[Int] = {
    println("B")
    Some(1)
  }

  def C: Option[String] = {
    println("C")
    None
  }

  def D: Option[Int] = {
    println("D")
    None
  }

 

코드

Monad 합성

    val monad: Option[(String, String)] = for {
      c <- C
      a <- A
    } yield (a, c)
    println(monad)
    
    // C
    // None

flatMap 에 의한 순차적인 합성은 fast fail 이므로 'A' 는 출력되지 않는다.

 

AssociativeBoth 합성 (Applicative)

    import zio.prelude._
    val ap: Option[(String, String)] = (C, A).mapN {
      case (c, a) => (c, a)
    }
    println(ap)
    
    // C
    // A
    // None

C 가 None 이지만 이후의 A 까지 모두 실행된다. 하나라도 None 이 있으면 최종 결과는 None 이다.

위의 코드는 아래처럼 작성할 수도 있다.

    import zio.prelude._
    val ap: Option[(String, String)] = C <*> A
    println(ap)

    // C
    // A
    // None

 

Validation 합성

    import zio.prelude._
    def cValid: Validation[String, String] = Validation.fromOption(C).mapError(_ => "C is empty")
    def aValid: Validation[String, String] = Validation.fromOption(A).mapError(_ => "A is empty")

    val validation: Validation[String, (String, String)] = Validation.validate(cValid, aValid)
    println(validation)

    // C
    // A
    // Failure(Chunk(),NonEmptyChunk(C is empty))

AssociativeBoth 처럼 모든 항목이 실행되면서, None 이 하나라도 있을 경우 해당 에러가 NonEmptyChunk 로 누적되어 기록된다. 

    import zio.prelude._
    def cValid: Validation[String, String] = Validation.fromOption(C).mapError(_ => "C is empty")
    def aValid: Validation[String, String] = Validation.fromOption(A).mapError(_ => "A is empty")
    def dValid: Validation[String, Int]    = Validation.fromOption(D).mapError(_ => "D is empty")

    val validation: Validation[String, (String, String, Int)] = Validation.validate(cValid, aValid, dValid)
    println(validation)

    // C
    // A
    // D
    // Failure(Chunk(),NonEmptyChunk(C is empty, D is empty))

 

AssociativeEither 합성

    import zio.prelude._
    val orElse: Option[Either[String, Int]] = A <+> B
    println(orElse)
    
    // A
    // Some(Left(A))
    import zio.prelude._
    val orElse: Option[Either[String, String]] = C <+> A
    println(orElse)
    
    // C
    // A
    // Some(Right(A))
    import zio.prelude._
    val orElse: Option[Either[String, Int]] = C <+> D
    println(orElse)

    // C
    // D
    // None

Boolean 의 OR 연산자 처럼 왼쪽이 success 이면 오른쪽 계산은 skip 한다.