New function pipeIf proposal

I would like to suggest a new method to be added to scala.util.ChainingOps, called pipeIf (see also https://stackoverflow.com/q/65955699/16673).

We use following implementation in our codebase a lot:

final implicit class ChainingOpsExt[A](private val self: A) {
  def pipeIf[B >: A](cond: Boolean)(f: A => B): B = if (cond) f(self) else self
  def pipeIf[B >: A](cond: A => Boolean)(f: A => B): B = if (cond(self)) f(self) else self
}

Examples:

val lowCase = true

"AbCdE".pipeIf(lowCase)(_.toLowerCase)
                         
"AbCdE".pipeIf(_(0).isUpper)(_.toLowerCase)

Note: The [B >: A] is useful when the function is widening a type (returning a base class of A). It seems to work in Scala 3 only, though, not in Scala 2.13:

trait T
class AA extends T
class BB extends T

new AA().pipeIf(false)(_ => new BB) // error in Scala 2.13 - found BB, required AA
2 Likes

Without pipeIf, one can write:

"AbCdE".pipe(x => if (lowCase) x.toLowerCase else x)

which just inlines the definition of pipeIf that you provide.

But I’d like to note that one can also write:

"AbCdE".pipe(if (lowCase) _.toLowerCase else identity))

thereby avoiding the need to use function literal syntax and invent a parameter name.

I don’t have a strong opinion either way on your proposal, I just wanted to make this small observation.

1 Like

That’s pretty neat. Though note that it desugars to

"AbCdE".pipe(if (lowCase) x => x.toLowerCase else x => identity(x))

Similarly,

def cond[B >: A](pf: PartialFunction[A, B]): B = pf.applyOrElse(self, identity)

for

"AbCdE".cond { case s if s(0).isUpper => s.toLowerCase }

There was a recent thread about syntax of one-line cases:

x match { case C(_) => y case _ => z }

where there is no semicolon to visually separate the cases. I observed that this syntax is preferred (in scalac) when the second case is a “default”.

x match { case C(_) => true case _ => false }
PartialFunction.cond(x) { case C(_) => true }

I think this use case deserves more support. The other utility doesn’t supply a default:

PartialFunction.condOpt(x) { case C(y) => y }  // Some(y) or None

where you would .getOrElse or Option.fold etc.