有状态函数流水线

Stateful function pipeline

代码自行解释。

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

我正在搜索此 check 函数的简单解决方案。

如何制作 purestateful 检查管道到管道?

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

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

实际上,如果您想在管道内编写它,并且不想添加 match 所必需的额外括号,您可以使用 commonly-enriched 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 只是向编译器表明它应该尽量避免创建额外的对象来进行调用)。

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

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

可能比 return 选项类型(而不是抛出)更可取:

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

如果您想在不引发异常的情况下传递错误消息,Either[A, B] 是一个不错的选择。这将需要更多的工作(使用 leftright 操作数)在管道中,但允许您传递一个长的更具描述性的错误消息,例如, Option[T] 可以' 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 是公正的,但它确实允许您流动行为。