在理解中使用不同的单子
Using different monads in for-comprehension
for-comprehensions 中可以使用不同的 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 中会很奇怪。
我假设你的意思是你现在有一个选项[Try[Post]]
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 {
post <- find(id)
} yield {
for {
tryOfPost <- permitted(post, userId)
} yield {
tryOfPost.copy(text = text)
}
}
将选项转换为 Try 以便您使用单一类型,这样做的缺点是会丢失 Try 中的错误与选项中的 None 之间的差异。学分 here 如何从选择到尝试。
for {
post <- find(id).fold[Try[Post]](Failure[Post](new OtherException))(Success(_))
permittedPost <- permitted(post, userId)
} yield {
permittedPost.copy(text = text)
}
您还可以查看 scalaz 中的 OptionT monad 转换器,以创建一个 OptionTtry 类型。
不过,从根本上说,Monad 不会以这种方式组合,至少不是一般的。
你只能在 for comprehension 中使用一种类型的 monad,因为它只是 flatMap
和 map
的语法糖。
如果您有一堆 monad(例如 Future[Option[A]]
),您可以使用 monad 转换器,但这不适用于此处。
针对您的情况的解决方案可能是使用一个 monad:从 Option
到 Try
或者从 Option
和 Try
到 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
for-comprehensions 中可以使用不同的 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 中会很奇怪。
我假设你的意思是你现在有一个选项[Try[Post]]
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 {
post <- find(id)
} yield {
for {
tryOfPost <- permitted(post, userId)
} yield {
tryOfPost.copy(text = text)
}
}
将选项转换为 Try 以便您使用单一类型,这样做的缺点是会丢失 Try 中的错误与选项中的 None 之间的差异。学分 here 如何从选择到尝试。
for {
post <- find(id).fold[Try[Post]](Failure[Post](new OtherException))(Success(_))
permittedPost <- permitted(post, userId)
} yield {
permittedPost.copy(text = text)
}
您还可以查看 scalaz 中的 OptionT monad 转换器,以创建一个 OptionTtry 类型。
不过,从根本上说,Monad 不会以这种方式组合,至少不是一般的。
你只能在 for comprehension 中使用一种类型的 monad,因为它只是 flatMap
和 map
的语法糖。
如果您有一堆 monad(例如 Future[Option[A]]
),您可以使用 monad 转换器,但这不适用于此处。
针对您的情况的解决方案可能是使用一个 monad:从 Option
到 Try
或者从 Option
和 Try
到 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