WeiChing 林煒清
WeiChing 林煒清

Reputation: 4469

Stateful function pipeline

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 .

How do I make a pure and stateful checking-pipe to the pipeline ?

Upvotes: 4

Views: 657

Answers (3)

Yuval Itzchakov
Yuval Itzchakov

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

Andy Hayden
Andy Hayden

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

Rex Kerr
Rex Kerr

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

Related Questions