Конвейер функции 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 непредвзято, но это позволяет вам течь поведение.

Другие вопросы по тегам