用 For-Comprehension 重写模式匹配
Re-writing Pattern Matching with For-Comprehension
给定以下类型:
sealed trait Pet {
val name: String
}
case class Dog(override val name: String) extends Pet
case class Cat(override val name: String) extends Pet
sealed trait Error
case object DBConnection extends Error
case object NoResults extends Error
我们编写了一个函数,可以根据宠物的名字搜索宠物。
def foo(petName: String): Either[Error, Pet] = {
val results: Either[Error, List[Pet]] = ??? // does not matter
val foundPet: Option[Pet] = results match {
case left @ Left(_) => None
case Right(ps) => ps.find(_.name == petName)
}
foundPet match {
case None => Left(NoResults)
case Some(p) => Right(p)
}
}
请忽略上述代码在数据库调用方面的任何改进。
理想情况下,我更愿意将上述代码编写为简单的 for comprehension
,利用 Either
monad。我相信模式匹配很容易阅读,但我怀疑 for
替代方案会更简洁。
我将如何重写上面的代码以供理解?我想我可以制作匹配 Either[Error, Pet]
的 return 类型的方法,但不确定。
可以将find
放入for-comprehension中,用toRight
转换为Either
。您还必须将它们转换为 RightProjection
s 以使其在理解中起作用(Either
本身没有 flatMap
和 map
)。
def foo(petName: String): Either[Error, Pet] = {
val results: Either[Error, List[Pet]] = ???
for {
pets <- results.right
pet <- pets.find(_.name == petName).toRight(NoResults).right
} yield pet
}
Scala Either[+A, +B]
type is that you have to do right or left projections to get a monad (respectively right or left biased). On the other hand, scalaz's \/[+A, +B]
的问题默认是单子的。要获得真正简洁的内容,使用 \/[+A, +B]
解决方案如下所示:
def foo(petName: String): \/[Error, Pet] = {
val results: \/[Error, List[Pet]] = ???
for {
pets <- results
results <- pets.find(_ == petName) \/> NoResults
} yield results
}
但是话又说回来,这是一个使用 for {} yield ...
不一定是最短解决方案的示例,这个 flatMap
给出了相同的结果:
results.flatMap(
_.find(_.name == petName) \/> NoResults
)
问题是 Scala Either
不是 monad 并且它没有偏见因此你不能在理解中使用它:你必须像其他海报提到的那样首先获得 LeftProject 或 RightProjection。
如果您的营业时间很少 scalaz
。 scalaz disjunction
(\/
) 是右偏的并且遵循所有单子法则。当你 map
超过它时,它会给你 right
值。
所以你的类型会变成
val results : \/[Error,List[Pet]]
和 results.map
会给你 List[Pet]
因为 scalaz 析取是右偏的。
this 也可能有帮助
Either.fold
是一种很好且可读的方式。而且你不需要 scalaz 来做到这一点。这是代码片段:
results.fold(
err => Left(err),
lst => lst.find(_.name == petName).map(Right(_)).getOrElse(Left(NoResults))
)
给定以下类型:
sealed trait Pet {
val name: String
}
case class Dog(override val name: String) extends Pet
case class Cat(override val name: String) extends Pet
sealed trait Error
case object DBConnection extends Error
case object NoResults extends Error
我们编写了一个函数,可以根据宠物的名字搜索宠物。
def foo(petName: String): Either[Error, Pet] = {
val results: Either[Error, List[Pet]] = ??? // does not matter
val foundPet: Option[Pet] = results match {
case left @ Left(_) => None
case Right(ps) => ps.find(_.name == petName)
}
foundPet match {
case None => Left(NoResults)
case Some(p) => Right(p)
}
}
请忽略上述代码在数据库调用方面的任何改进。
理想情况下,我更愿意将上述代码编写为简单的 for comprehension
,利用 Either
monad。我相信模式匹配很容易阅读,但我怀疑 for
替代方案会更简洁。
我将如何重写上面的代码以供理解?我想我可以制作匹配 Either[Error, Pet]
的 return 类型的方法,但不确定。
可以将find
放入for-comprehension中,用toRight
转换为Either
。您还必须将它们转换为 RightProjection
s 以使其在理解中起作用(Either
本身没有 flatMap
和 map
)。
def foo(petName: String): Either[Error, Pet] = {
val results: Either[Error, List[Pet]] = ???
for {
pets <- results.right
pet <- pets.find(_.name == petName).toRight(NoResults).right
} yield pet
}
Scala Either[+A, +B]
type is that you have to do right or left projections to get a monad (respectively right or left biased). On the other hand, scalaz's \/[+A, +B]
的问题默认是单子的。要获得真正简洁的内容,使用 \/[+A, +B]
解决方案如下所示:
def foo(petName: String): \/[Error, Pet] = {
val results: \/[Error, List[Pet]] = ???
for {
pets <- results
results <- pets.find(_ == petName) \/> NoResults
} yield results
}
但是话又说回来,这是一个使用 for {} yield ...
不一定是最短解决方案的示例,这个 flatMap
给出了相同的结果:
results.flatMap(
_.find(_.name == petName) \/> NoResults
)
问题是 Scala Either
不是 monad 并且它没有偏见因此你不能在理解中使用它:你必须像其他海报提到的那样首先获得 LeftProject 或 RightProjection。
如果您的营业时间很少 scalaz
。 scalaz disjunction
(\/
) 是右偏的并且遵循所有单子法则。当你 map
超过它时,它会给你 right
值。
所以你的类型会变成
val results : \/[Error,List[Pet]]
和 results.map
会给你 List[Pet]
因为 scalaz 析取是右偏的。
this 也可能有帮助
Either.fold
是一种很好且可读的方式。而且你不需要 scalaz 来做到这一点。这是代码片段:
results.fold(
err => Left(err),
lst => lst.find(_.name == petName).map(Right(_)).getOrElse(Left(NoResults))
)