Конвейер функции Stateful
Код объясняет сам.
val s = Seq(1,1,1)
val res: Seq[Int] = s.map(...)
.check(count how many 1s, if > 2 throw Exception)
.map(...)
Я ищу простое решение для этого check
функция
- я могу использовать
map
а такжеclosure
считать и бросать, но я хочу чистую функцию. - я могу использовать
filter
а такжеsize
или жеreduce
, но он возвращает значение и не возобновляется со следующими картами.
Как сделать чистую и сохраняющую состояние контрольную трубу к конвейеру?
3 ответа
Одним из решений является сопоставление с шаблоном, поэтому проверка может стать:
> 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]]
Бросать исключение, возможно, не чисто. Если бы вы вместо этого использовали монадическую форму обработки ошибок, вы бы сделали что-то вроде этого:
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
вещи просто указывают компилятору, что он должен стараться избегать создания дополнительного объекта для выполнения вызова).
Если вы хотите передать сообщение об ошибке без исключения, хорошим кандидатом будет Either[A, B]
, Это потребует дополнительной работы (работа с left
или же right
операнды) по конвейеру, но позволяет передавать длинное более описательное сообщение об ошибке, где, например, 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
непредвзято, но это позволяет вам течь поведение.