Reputation: 4469
The code explains itself.
val s = Seq(1,1,1)
val res: Seq[Int] = s.map(...)
.check(count how many 1s, if > 2 throw Exception)
.map(...)
I am searching the simple solution to this check
function .
map
and closure
to count and throw, but I want pure function.filter
and size
or reduce
, but it return a value and not resumable with following maps.How do I make a pure and stateful checking-pipe to the pipeline ?
Upvotes: 4
Views: 657
Reputation: 149538
If you want to pass along an error message without throwing an exception, a good candidate would be Either[A, B]
. This would require some more work (working with left
or right
operands) down the pipeline, but allows you to pass a long a more descriptive error message, where for example, Option[T]
can't convey:
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
This can be slightly inconvenient as Either
is unbiased, but it does allow you to flow the behavior.
Upvotes: 1
Reputation: 375445
One solution is to pattern match, so check would become:
> 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)
It may be preferable to return an Option type (instead of throwing):
> Seq(1, 1, 1) match {
case ss if (ss.count(_ == 1) <= 2) => Option(ss)
case _ => None
}
None: Option[Seq[Int]]
Upvotes: 4
Reputation: 167891
Throwing an exception is arguably not pure. If you instead were using a monadic form of error handling, you'd do something like this:
Option(s.map(foo)).
filter(m => m.count(_ == 1) < 2).
map{ s =>
s.map(bar)
.filter(baz)
...
}
As it is, if you want to compose it within the pipeline, and you don't want to add extra parentheses as is necessary with match
, you can use the commonly-enriched tap
method:
implicit class TapAnything[A](private val a: A) extends AnyVal {
def tap[U](f: A => U): A = { f(a); a }
}
Now you can
s.map(...)
.tap(self => if (self.count(_ == 1) > 1) throw new Exception)
.map(...)
...
(note: the private val
+ extends AnyVal
stuff is just to indicate to the compiler that it's supposed to try to avoid creating an extra object to make the call).
Upvotes: 5