简体   繁体   English

重写模式匹配与理解

[英]Re-writing Pattern Matching with For-Comprehension

Given the following types: 给出以下类型:

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

We write a function that searches for a pet by its name. 我们编写了一个按名称搜索宠物的函数。

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) 
  }
}

Please ignore any improvements to the above code with respect to the database call. 请忽略有关数据库调用的上述代码的任何改进。

Ideally, I'd prefer to write the above code as a simple for comprehension , taking advantage of the Either monad. 理想情况下,我更喜欢将上面的代码写成一个简单for comprehension ,利用Either monad。 The pattern matching is easy to read, I believe, but the for alternative would be more concise, I suspect. 我相信,模式匹配很容易阅读,但我怀疑for替代方案会更简洁。

How would I re-write the above code with a for-comprehension? 如何用for-comprehension重写上面的代码? I suppose that I could just make methods that match the return type of Either[Error, Pet] , but was not sure. 我想我可以制作与Either[Error, Pet]的返回类型相匹配的方法,但不确定。

Problem is Scala Either isn't a monad and it's not biased hence you can't use it in a for-comprehension: you have to get a LeftProject or RightProjection first as other poster mentioned. 问题是Scala Either不是monad而且它没有偏见因此你不能在for-comprehension中使用它:你必须首先得到一个LeftProject或RightProjection,就像其他提到的海报一样。

If you're open for little scalaz . 如果你对小scalaz开放。 scalaz disjunction ( \\/ ) is right biased and follows all monad laws. scalaz disjunction\\/ )是正确的偏见并遵循所有monad法则。 when you map over it it gives you right value. 当你map它时,它给你right价值。

so your type will become 所以你的类型将成为

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

and results.map will give you List[Pet] because scalaz disjunction is right biased. results.map会给你List[Pet]因为scalaz脱节是正确的偏见。

this may be helpful as well 可能也有帮助

You can put the find into the for-comprehension and use toRight to convert it to Either . 您可以将find放入for-comprehension并使用toRight将其转换为Either You also must convert these to RightProjection s in order for it to work in the for-comprehension ( Either does not have flatMap and map on its own). 您还必须将这些转换为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
}

The problem with Scala's Either[+A, +B] type is that you have to do right or left projections to get a monad (respectively right or left biased). Scala的Either[+A, +B]类型的问题是你必须做右或左投影来获得一个monad(分别是右偏或左偏)。 On the other hand, scalaz's \\/[+A, +B] is monadic by default. 另一方面,scalaz的\\/[+A, +B]默认为monadic。 To get something really concise, with \\/[+A, +B] the solution would look like this: 为了得到一些非常简洁的东西,用\\/[+A, +B]解决方案看起来像这样:

def foo(petName: String): \/[Error, Pet] = {
  val results: \/[Error, List[Pet]] = ???

  for {
    pets <- results
    results <- pets.find(_ == petName) \/> NoResults
  } yield results
}

But then again, it's an example where using for {} yield ... isn't necessarily the shortest solution, this flatMap gives the same result: 但话说回来,这是一个使用for {} yield ...的例子,不一定是最短的解决方案,这个flatMap给出了相同的结果:

results.flatMap(
  _.find(_.name == petName) \/> NoResults
)

Either.fold is a nice and readable way. Either.fold是一个很好的和可读的方式。 And you don't need scalaz to do that. 你不需要scalaz就可以做到这一点。 Here is the code snippet: 这是代码片段:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM