Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/library/scala/util/ChainingOps.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package scala
package util

trait ChainingSyntax {
implicit final def scalaUtilChainingOps[A](a: A): ChainingOps[A] = new ChainingOps(a)
}

/** Adds chaining methods `tap` and `pipe` to every type.
*/
final class ChainingOps[A](val self: A) extends AnyVal {
/** Applies `f` to the value for its side effects, and returns the original value.
*
* {{{
* val xs = List(1, 2, 3)
* .tap(ys => println("debug " + ys.toString))
* // xs == List(1, 2, 3)
* }}}
*
* @param f the function to apply to the value.
* @tparam U the result type of the function `f`.
* @return the original value `self`.
*/
def tap[U](f: A => U): self.type = {
f(self)
self
}

/** Converts the value by applying the function `f`.
*
* {{{
* val times6 = (_: Int) * 6
* val i = (1 - 2 - 3).pipe(times6).pipe(scala.math.abs)
* // i == 24
* }}}
*
* Note: `(1 - 2 - 3).pipe(times6)` may have a small amount of overhead at
* runtime compared to the equivalent `{ val temp = 1 - 2 - 3; times6(temp) }`.
*
* @param f the function to apply to the value.
* @tparam B the result type of the function `f`.
* @return a new value resulting from applying the given function
* `f` to this value.
*/
def pipe[B](f: A => B): B = f(self)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other languages name this |>. Would be great to do so here too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mentioned here: #6767 (comment)

Copy link
Contributor

@Jasper-M Jasper-M Aug 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reasoning is that symbolic operators are bad. Though, as a small datapoint: I have no idea what <<=, <+= or <++= mean, I only understand >>= if you tell me its name, I always mix up /: and :\, but |> is just obvious to me, and is better at visually separating steps than pipe. And you have to explicitly import this anyway so having this symbolic alias wouldn't be the worst thing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally if we can ignore backward compat is get rid of match as keyword and make it a function named match. |> is basically match.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add |> too.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seconding @japgolly . I'm not for symbols in general but this is a very very special ubiquitous one. bash uses something similar, F# has it, PowerShell too.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ScalaWilliam You can add OCaml & ReasonML to that list.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe one of you should send a new pull request or contributor thread and get consensus around it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As advised by @eed3si9n , here's a PR: #7326

fyi @japgolly @RawToast @hepin1989

}
8 changes: 8 additions & 0 deletions src/library/scala/util/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package scala

package object util {
/**
* Adds chaining methods `tap` and `pipe` to every type. See [[ChainingOps]].
*/
object chaining extends ChainingSyntax
}
28 changes: 28 additions & 0 deletions test/junit/scala/util/ChainingOpsTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package scala.util

import org.junit.Assert._
import org.junit.Test

class ChainingOpsTest {
import scala.util.chaining._

@Test
def testAnyTap: Unit = {
var x: Int = 0
val result = List(1, 2, 3)
.tap(xs => x = xs.head)

assertEquals(1, x)
assertEquals(List(1, 2, 3), result)
}

@Test
def testAnyPipe: Unit = {
val times6 = (_: Int) * 6
val result = (1 - 2 - 3)
.pipe(times6)
.pipe(scala.math.abs)

assertEquals(24, result)
}
}