使用不同的monad进行理解

时间:2015-10-28 20:33:16

标签: scala monads

可以在for -reherehension中使用不同的monad吗?这是使用map

的代码
case class Post(id: Int, text: String)

object PostOps {
  def find(id: Int) : Option[Post] = if (id == 1) Some(Post(1, "text")) else None

  def permitted(post: Post, userId: Int) : Try[Post] = if (userId == 1) Success(post) else Failure(new UnsupportedOperationException)

  def edit(id: Int, userId : Int, text: String) = find(id).map(permitted(_, userId).map(_.copy(text = text))) match {
      case None => println("Not found")
      case Some(Success(p)) => println("Success")
      case Some(Failure(_)) => println("Not authorized")
  }
}

for-comprehension的简单版本由于显而易见的原因不起作用,但它是否可以使其与一些额外的代码一起使用?我知道在C#中它是可能的,所以如果它不在Scala中会很奇怪。

2 个答案:

答案 0 :(得分:4)

你只能使用一种类型的monad来理解,因为它只是flatMapmap的语法糖。

如果你有一堆monad(例如Future[Option[A]])你可以使用monad变换器,但这不适用于此。

针对您的案例的解决方案可能是使用一个monad:从Option转到Try或从OptionTry转到Either[String, A]

def tryToEither[L, R](t: Try[R])(left: Throwable => L): Either[L, R] = 
  t.transform(r => Success(Right(r)), th => Success(Left(left(th)))).get

def edit(id: Int, userId: Int, text: String) = {
  val updatedPost = for {
    p1 <- find(id).toRight("Not found").right
    p2 <- tryToEither(permitted(p1, userId))(_ => "Not Authorized").right
  } yield p2.copy(text = text)
  updatedPost match {
    case Left(msg) => println(msg)
    case Right(_)  => println("success")
  }
}

您可以定义错误类型,而不是使用String,这样您就可以使用Either[Error, A]

sealed trait Error extends Exception
case class PostNotFound(userId: Int) extends Error
case object NotAuthorized extends Error 

答案 1 :(得分:3)

我认为你的意思是你现在有一个选项[试试[发布]]

for

可以通过几种方式完成。

嵌套fors:

find(id).map(permitted(_, userId).map(_.copy(text = text))) match {
  case None => println("Not found")
  case Some(Success(p)) => println("Success")
  case Some(Failure(_)) => println("Not authorized")
}

将选项转换为试用以便您使用单一类型,这样做的缺点是会丢失Try中的错误与Option中的None之间的差异。有关如何从选项到尝试的信用here

  for {
    post <- find(id)
  } yield {
    for {
      tryOfPost <- permitted(post, userId)
    } yield {
      tryOfPost.copy(text = text)
    }
  }

您还可以在scalaz中查看OptionT monad转换器以创建一个OptionTTry类型。

但从根本上说,Monads并不是这样构成的,至少不是一般的。