Writerモナドの使い道 [ログ出力]

def selectIdFromDb(): Writer[List[String], Int] = for {
  _ <- Writer.tell(List("db access start."))
  _ <- Writer.tell(List("TODO: db access 実装"))
  r <- Writer.value[List[String], Int](1)
  _ <- Writer.tell(List("db access   end."))
} yield r

def getInfoFromApiById(id: Int): Writer[List[String], String] = for {
  _ <- Writer.tell(List("db access start."))
  _ <- Writer.tell(List("TODO: api access 実装"))
  r <- Writer.value[List[String], String](s"api info. id=[$id]")
  _ <- Writer.tell(List("db access   end."))
} yield r

val result = for {
  id <- selectIdFromDb()
  info <- getInfoFromApiById(id)
} yield info

// ログ部分と結果部分に別れる
val (logs, infoResult) = result.run

// ログ出力
logs.foreach(println)
/**
  * db access start.
  * TODO: db access 実装
  * db access   end.
  * db access start.
  * TODO: api access 実装
  * db access   end.
  */

// 結果出力
println(infoResult) // api info. id=[1]

プログラミングhaskell 8章 version scala 〜後半〜

前半はこちら

def token[T](p: Parser[T]): Parser[T] =
  for {
    _ <- space
    v <- p
    _ <- space
  } yield v

def identifier: Parser[String] = token(ident)
def natural: Parser[Int] = token(nat)
def symbol(s: String): Parser[String] = token(string(s))

println((for {
  _  <- symbol("[")
  n  <- natural
  nx <- many(for {
         _  <- symbol(",")
         lr <- natural
        } yield lr)
  _  <- symbol("]")
} yield n :: nx).run("    [     1,   2, 3,4,   5 ]"))

def expr: Parser[Int] =
  for {
    t <- term
    r <- (for {
           _ <- symbol("+")
           e <- expr
         } yield t + e) +++
         (for {
           _ <- symbol("-")
           e <- expr
         } yield t - e) +++ pure(t)
  } yield r

def term: Parser[Int] =
  for {
    f <- factor
    r <- (for {
            _ <- symbol("*")
            t <- term
          } yield f * t) +++
         (for {
            _ <- symbol("/")
            t <- term
          } yield f / t) +++ pure(f)
  } yield r

def factor: Parser[Int] =
  (for {
    _ <- symbol("(")
    e <- expr
    _ <- symbol(")")
  } yield e) +++ natural

def eval(s: String): Int =
  expr.run(s).map(_._2).get

println(eval("2*3+4"))
println(eval("2*(3+4)"))
println(eval("(1 + 2) * (7 - 2) / 5"))

プログラミングhaskell 8章 version scala 〜前半〜

  type Parser[T] = StateT[Option, String, T]

  def pure[T](x: T): Parser[T] =
    StateT.pure[Option, String, T](x)

  def failure: Parser[String] =
    StateT[Option, String, String](_ => None)

  def item: Parser[String] =
    StateT{
      case "" => None
      case xs => Some(xs.tail, xs.head.toString)
    }

  implicit class ParserOps[T](p: Parser[T]) {
    def +++(q: Parser[T]): Parser[T] =
      StateT[Option, String, T] { inp =>
        p.run(inp) orElse q.run(inp)
      }
  }
  println((item +++ pure("d")).run("abc"))
  println((failure +++ pure("d")).run("abc"))
  println((failure +++ failure).run("abc"))

  def item2 = for {
    s1 <- item
    _  <- item
    s2 <- item
  } yield s1 + s2
  println(item2.run("abcd"))

  def sat(p: String => Boolean): Parser[String] =
    for {
      x <- item
      r <- if (p(x)) pure(x) else failure
    } yield r
  def digit = sat(_.matches("^\\d$"))
  def lower = sat(_.matches("^[a-z]$"))
  def upper = sat(_.matches("^[A-Z]$"))
  def letter = sat(_.matches("^[a-zA-Z]$"))
  def alphanum = sat(_.matches("^[0-9a-zA-Z]$"))
  def char(x: String) = sat(_ == x)

  println(sat(x => x.matches("^1$")).run("22"))

  def string(str: String): Parser[String] = str match {
    case "" => pure("")
    case _ => for {
      x  <- char(str.head.toString)
      xs <- string(str.tail.mkString)
    } yield x + xs
  }

  println(string("abc").run("abcdef"))

  def many[T](p: Parser[T]): Parser[List[T]] = many1(p) +++ pure(Nil)
  def many1[T](p: Parser[T]): Parser[List[T]] =
    for {
      v  <- p
      vs <- many(p)
    } yield v :: vs

  println(many(digit).run("123abc"))
  println(many(digit).run("abcdef"))

  def ident: Parser[String] =
    for {
      x  <- lower
      xs <- many(alphanum)
    } yield x + xs.mkString

  def nat: Parser[Int] =
    many1(digit).map(_.mkString.toInt)

  def space: Parser[String] =
    for {
      _ <- many(sat(_ == " "))
      r <- pure("")
    } yield r

  println(nat.run("123abc"))
  println(space.run("   abc"))

Validated(Applicative)のsequence

ListのEitherのsequenceは最初のエラーだけしかかえってこない。
ListのValidatedのsequenceはすべてのエラーがかえってくる。

  val eithers: List[Either[String, Int]]          = List(Right(1), Left("error1"), Right(2), Left("error2"), Right(3))
  val validateds: List[ValidatedNel[String, Int]] = eithers.map(_.toValidatedNel)

  println(eithers.sequence)    // Left(error1)
  println(validateds.sequence) // Invalid(NonEmptyList(error1, error2))

Readerモナドの使い道 [DI]

catsを使用したReaderモナドの使い道例として、DIのサンプル

  trait DbAccessor {
    def select: List[String]
    def count: Int
  }

  object DbAccessorImpl extends DbAccessor {
    override def select: List[String] = {
      // TODO: dbAccess処理
      List.empty
    }
    override def count: Int = {
      // TODO: dbAccess処理
      0
    }
  }

  object DbAccessorMock extends DbAccessor {
    override def select: List[String] = List("a", "c", "d")
    override def count: Int = 5
  }

  val selectR = Reader[DbAccessor, List[String]](db => db.select)
  val countR = Reader[DbAccessor, Int](db => db.count)

  val tupleR = for {
    list <- selectR
    int <- countR
  } yield (int, list)

  // テスト時はmockを使う
  println(tupleR.run(DbAccessorMock)) // (5,List(a, c, d))
  // 実装はdbアクセスする
  println(tupleR.run(DbAccessorImpl)) // (0,List())