【问题标题】:Scala shortcut first solution in a for-comprehension list理解列表中的 Scala 快捷方式第一个解决方案
【发布时间】:2021-02-11 21:12:33
【问题描述】:

我有一个编码草稿,只要它提供正确的答案,它就可以工作。但从美学方面来说,它可以改进,我猜! 目标:在许多可能的解决方案列表中找到第一个解决方案。当找到第一个解决方案时,不要进一步计算。在实际应用中,每个解/非解的计算肯定会更复杂。 不喜欢:Solution=LeftNoSolution=Right 别名是反直觉的,因为 Right 通常代表成功,这里 Left 和 Right 被交换(因为在技术上使用 @ 987654327@ 仅Left 快捷方式理解列表) 有没有改进这个实现的好方法?还是其他解决方案?

package playground

object Test {

  def main(args: Array[String]): Unit = {
    test
  }

  val Solution = Left
  val NoSolution = Right

  def test: Unit = {
    {

      // Find the first solution in a list of computations and print it out

      val result = for {
        _ <- if (1 == 2) Solution("impossible") else NoSolution()
        _ <- NoSolution()
        _ <- NoSolution(3)
        _ <- Solution("*** Solution 1 ***")
        _ <- NoSolution("oh no")
        _ <- Solution("*** Solution 2 ***")
        x <- NoSolution("no, no")
      } yield x
      if (result.isLeft)
        println(result.merge)      // Prints:  *** Solution 1 ***
    }
  }
}

【问题讨论】:

  • 这样的事情怎么样:data.iterator.map(computeSolution).collecFirst { case Right(solution) =&gt; solution } 其中data 是要传递给computeSolution 的输入的列表,它将返回Either。这样做的坏处是,如果全部失败,我们就会丢失错误消息。
  • 您的问题和代码有点令人困惑。您说目标是“在许多可能的列表中找到第一个解决方案...” 但您的代码中没有List。现实世界的解决方案是否已经以反向Either 格式包装,或者只是为了for 理解而添加的?如果是后者,每个“解决方案”如何测试Left/Right 分配?如果没有找到正确的解决方案,则发布的代码不会执行任何操作。这是期望的行为吗? Either 结果的 NoSolution 一侧是否有任何值得保留/报告的内容?
  • @jwvh:“代码中的列表”指的是for { ... }中的项目;现实世界的问题不会被包裹在一个反向Either 中,这只是我找不到更好的建议;解决方案测试例如用于演示的琐碎“1 == 2”(在测试#1中),但可以变大;如果没有找到正确的解决方案,代码什么也不做,因为这是目的; NoSolution 细节不是必需的,因此它们甚至可以完全省略。与第一个异常快捷方式的异常处理程序相比,这里第一个肯定/解决方案应该快捷方式;无需深入研究非解决方案

标签: scala monads


【解决方案1】:

所以您正在寻找“monaduck”的东西:即具有 flatMap/map 但不一定遵守任何单子定律(Scala 甚至不要求 flatMap 具有单子形状:链脱糖后只需进行类型检查);参看。鸭子打字。

trait Trial[+Result] {
  def result: Option[Result]

  def flatMap[R >: Result](f: Unit => Trial[R]): Trial[R]
  def map[R](f: Result => R): Trial[R]
}

case object NoSolution extends Trial[Nothing] {
  def result = None
  def flatMap[R](f: Unit => Trial[R]): Trial[R] = f(())
  def map[R](f: Result => R): Trial[R] = this
}

case class Solution[Result](value: Result) extends Trial[Result] {
  def result = Some(value)
  def flatMap[R >: Result](f: Unit => Trial[R]): Trial[R] = this
  def map[R](f: Result => R): Trial[R] = Solution(f(value))
}
scala> for {
     |   _ <- if (1 == 2) Solution("nope") else NoSolution
     |   _ <- NoSolution
     |   _ <- Solution("yay!")
     |   _ <- NoSolution
     |   x <- Solution("nope")
     | } yield x
res0: Trial[String] = Solution(yay!)

scala> for {
     |   _ <- if (1 == 2) Solution("nope") else NoSolution
     |   _ <- NoSolution
     |   _ <- Solution("yay!")
     |   x <- NoSolution
     | } yield x
res1: Trial[String] = Solution(yay!)

scala> for {
     |   _ <- if (1 == 2) Solution("nope") else NoSolution
     |   x <- NoSolution
     | } yield x
res2: Trial[String] = NoSolution

很明显,一元定律被违反了:我们唯一可以用于pure 的是Solution,但是

scala> val f: Unit => Trial[Any] = { _ => NoSolution }
f: Unit => Trial[Any] = $Lambda$107382/0x00000008433be840@6c0e35d7

scala> Solution(5).flatMap(f)
res7: Trial[Any] = Solution(5)

scala> f(5)
<console>:13: warning: a pure expression does nothing in statement position
       f(5)
         ^
res8: Trial[Any] = NoSolution

没有 Scala 愿意将任何纯值转换为 Unit,这甚至不会进行类型检查,但仍然会破坏左身份。

【讨论】:

  • 老实说应该将左侧身份中的f 设为Trial[Nothing]...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-02-27
  • 2011-09-08
  • 2021-05-20
  • 1970-01-01
  • 2023-04-03
  • 2021-02-08
  • 2012-03-31
相关资源
最近更新 更多