重写模式匹配与理解

时间:2015-04-09 15:15:39

标签: scala monads

考虑以下类型:

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替代方案会更简洁。

如何用for-comprehension重写上面的代码?我想我可以创建与Either[Error, Pet]的返回类型匹配的方法,但不确定。

4 个答案:

答案 0 :(得分:6)

问题是Scala Either不是monad并且它没有偏见因此你不能在for-comprehension中使用它:你必须首先得到一个LeftProject或RightProjection提到的其他海报。

如果您为小scalaz开放。 scalaz disjunction\/)是正确的偏见,遵循所有monad法则。当您map覆盖它时,它会为您提供right值。

所以你的类型将成为

 val results : \/[Error,List[Pet]]

results.map会给你List[Pet],因为scalaz disjunction是正确的偏见。

this也可能有用

答案 1 :(得分:3)

您可以将find置于for-comprehension中,并使用toRight将其转换为Either。您还必须将这些内容转换为RightProjection,以使其在for-comprehension中工作(Either本身没有flatMapmap

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
}

答案 2 :(得分:2)

Scala的Either[+A, +B]类型的问题是你必须做右或左投影来获得monad(分别是右偏或左偏)。另一方面,scalaz的\/[+A, +B]默认是monadic。为了得到一些非常简洁的东西,使用\/[+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
)

答案 3 :(得分:0)

Either.fold是一种不错且易读的方式。你不需要scalaz就可以做到这一点。以下是代码段:

results.fold(
  err => Left(err),
  lst => lst.find(_.name == petName).map(Right(_)).getOrElse(Left(NoResults))
)