【问题标题】:Stateful function pipeline有状态的函数管道
【发布时间】:2016-09-26 10:47:58
【问题描述】:

代码自行解释。

val s = Seq(1,1,1)
val res: Seq[Int] = s.map(...)
                     .check(count how many 1s, if > 2 throw Exception)
                     .map(...)

我正在寻找这个check 函数的简单解决方案。

  • 我可以使用mapclosure来计数和抛出,但我想要纯函数。
  • 我可以使用filtersizereduce,但它会返回一个值,并且无法通过以下地图恢复。

如何为管道创建一个有状态检查管道?

【问题讨论】:

  • 编写一个简单的函数,它将整个集合作为输入,使用reduce在其中进行检查,如果检查成功则返回Some(collection),否则返回None。那么它就会很纯净。您可以将检查注入为 lambda。

标签: scala functional-programming closures pipeline purely-functional


【解决方案1】:

抛出异常可以说是不纯粹的。如果您改为使用单子形式的错误处理,您将执行以下操作:

Option(s.map(foo)).
  filter(m => m.count(_ == 1) < 2).
  map{ s =>
    s.map(bar)
     .filter(baz)
     ...
  }

事实上,如果你想在管道中组合它,并且你不想在match 中添加额外的括号,你可以使用常用的丰富的tap 方法:

implicit class TapAnything[A](private val a: A) extends AnyVal {
  def tap[U](f: A => U): A = { f(a); a }
}

现在可以

s.map(...)
 .tap(self => if (self.count(_ == 1) > 1) throw new Exception)
 .map(...)
 ...

(注意:private val + extends AnyVal 的内容只是向编译器表明它应该尽量避免创建额外的对象来进行调用)。

【讨论】:

  • 我猜你可以做一个类似的tapOption 而不是扔:)(很好的答案!)
  • @AndyHayden - 如果它正在改变类型,这并不是一个真正的点击。否则它是一个管道,通常写成|&gt; 并且通常完成,但你当然可以做s |&gt; {x =&gt; if (x.count(_ == 1) &lt; 2) Some(x) else None} 内联。 (可能需要在s 之后添加. 以使所有内容的优先级都正确。)但是,是的,可以进行验证模拟(也许optionIftapOption 更好)。
【解决方案2】:

一种解决方案是模式匹配,因此检查将变为:

> Seq(1, 1) match {
    case ls if (ls.count(_ == 1) <= 2) => ls
    case _ => throw new Exception("oh no!")
  }
List(1, 1): Seq[Int]


> Seq(1, 1, 1) match {
    case ls if (ls.count(_ == 1) <= 2) => ls
    case _ => throw new Exception("oh no!")
  }
java.lang.Exception: oh no!
  $.<init>(Main.scala:177)
  $.<clinit>(Main.scala:-1)

最好返回一个 Option 类型(而不是抛出):

> Seq(1, 1, 1) match {
    case ss if (ss.count(_ == 1) <= 2) => Option(ss)
    case _ => None
  }
None: Option[Seq[Int]]

【讨论】:

    【解决方案3】:

    如果您想在不引发异常的情况下传递错误消息,一个不错的选择是Either[A, B]。这将需要更多的工作(使用leftright 操作数)在管道中,但允许您传递更长的更具描述性的错误消息,例如Option[T] 无法传达:

    val x = Seq(1,1,0)
            .map(_ * 3)
            .map(i => if (i > 2) Right(i) else Left("Was smaller than 2"))
            .map(_.right.map(_ * 3))
    
    x.foreach {
      case Right(i) => println("Yay, right")
      case Left(error) => println(error)
    }
    

    scala> :paste
    // Entering paste mode (ctrl-D to finish)
    
      val x = Seq(1,1,0)
        .map(_ * 3)
        .map(i => if (i > 2) Right(i) else Left("Was smaller than 2"))
        .map(_.right.map(_ * 3))
    
      x.foreach {
        case Right(i) => println("Yay, right")
        case Left(error) => println(error)
      }
    
    // Exiting paste mode, now interpreting.
    
    Yay, right
    Yay, right
    Was smaller than 2
    

    这可能有点不方便,因为Either 是公正的,但它确实允许您进行行为。

    【讨论】:

    • 当使用Either进行错误处理时,约定Right正确,Left错误。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-24
    相关资源
    最近更新 更多