Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Piping methods on Any similar to scalaz "|>", Ruby "tap" #5324

Closed
scabug opened this issue Dec 17, 2011 · 13 comments · Fixed by scala/scala#7007
Closed

Piping methods on Any similar to scalaz "|>", Ruby "tap" #5324

scabug opened this issue Dec 17, 2011 · 13 comments · Fixed by scala/scala#7007

Comments

@scabug
Copy link

scabug commented Dec 17, 2011

Introduction

The piping methods have proven to be extremely useful in other languages: .with - Groovy; .tap - Ruby. It's a known fact that a lot of people get surprised not to meet any similar feature in Scala as well as that because of that a lot of them introduce custom implicit conversions.

Examples

Let's consider two examples of how the same purpose of outputting the following text can be achieved with and without the proposed feature:

Source: List(1, 2, 3, 4, 5)
Result: [2,4]

Example one: with proposed methods tap and convert

Seq(1, 2, 3, 4, 5)
  .tap(it => println("Source: " + it))
  .filter(_ % 2 == 0)
  .convert(it => if (it.nonEmpty) "[" + it.mkString(",") + "]" else "")
  .tap(it => println("Result: " + it))

Please notice how factored, maintainable and easy on the eye the above code is

Example two: how one has to do it currently

val tmp1 = Seq(1, 2, 3, 4, 5)
println("Source: " + tmp1)
val tmp2 = tmp1.filter(_ % 2 == 0)
val tmp3 = if (tmp2.nonEmpty) "[" + tmp2.mkString(",") + "]" else ""
println("Result: " + tmp3)

Notice that you have to stop and think 3 times on naming the obviously redundant intermediate variables. Also notice how unreadable, unfactored and harder to manage that code is.

Implementation

Currently for the first example to work you'll need a following implicit conversion. You can also learn the specification of methods from there:

implicit def wrapAny[Source](source: Source) = new AnyWrapper(source)
class AnyWrapper[Source](source: Source) {
  def tap[Result](closure: Source => Result): Source = {
    closure(source)
    source
  }
  def convert[Result](closure: Source => Result): Result = {
    closure(source)
  }
}

The suggestions:

  1. Introduce a method with codename "convert" (or "use" or "pipe" or "pipeTo" as suggested by some) which executes a closure on the object and returns the result of that closure
  2. Introduce a method with codename "tap" (or "effect" as suggested by some) which executes a closure on the object and returns the object itself
  3. Not to inherit the scalaz operator-style "|>" title as it is considered disturbing by many
  4. Discuss details such as names in this thread

This suggestion originates from the following discussion on StackOverflow: http://stackoverflow.com/questions/8537992/with-alternative-in-scala/8538277

@scabug
Copy link
Author

scabug commented Dec 17, 2011

Imported From: https://issues.scala-lang.org/browse/SI-5324?orig=1
Reporter: Nikita Volkov (mojojojo)

@scabug
Copy link
Author

scabug commented Dec 17, 2011

@paulp said:
For reference, there's an implementation of this in trunk I use in the repl, which looks pretty much identical except for the type parameter names.

  implicit def enrichAnyRefWithTap[T](x: T) = new TapMaker(x)
  class TapMaker[T](x: T) {
    def tap[U](f: T => U): T = {
      f(x)
      x
    }
  }

@scabug
Copy link
Author

scabug commented Dec 17, 2011

@Ichoran said:
Since I suggested effect, I feel qualified to recommend tap instead; it's shorter and gets the point across.

I don't like convert, because it's too long and things like x.convert(_+2) don't really seem like much of a conversion. I like pipe or ap or app or as or be (not great grammar on that last one, but it's short and gets the point across...).

@scabug
Copy link
Author

scabug commented Dec 23, 2011

Nikita Volkov (mojojojo) said:
I support as as a replacement of convert since it has very close semantics and is really succinct. I don't support pipe since its semantics are coarse and misleading. ap and app are abbreviations and I don't remember any abbreviations being used in the standard library. Semantics of be are too broad.

Actually I came up with as on my own and now that I see that Rex has already posted it I support his suggestion twice as much )

@dwijnand
Copy link
Member

Can one (such as myself) PR this? Or should it be rejected and closed?

@SethTisue
Copy link
Member

seems like an open question whether a PR on this would ultimately succeed.

naming-wise, I like .tap fine... don't much care for either convert or as, but not sure what else to suggest

@SethTisue SethTisue changed the title Piping methods on Any similar to scalaz "|>" Piping methods on Any similar to scalaz "|>", Ruby "tap" Feb 17, 2018
eed3si9n added a commit to eed3si9n/scala that referenced this issue Jun 11, 2018
Fixes scala/bug#5324

This implements two implicit classes `AnyTap` and `AnyPipe`, which enrich every type `A` to inject `tap` and `pipe` method respectively.

```scala
scala> val xs = List(1, 2, 3).tap(ys => println("debug " + ys.toString))
debug List(1, 2, 3)
xs: List[Int] = List(1, 2, 3)

scala> val s = List(1, 2, 3).pipe(xs => xs.mkString(","))
s: String = 1,2,3
```
@eed3si9n
Copy link
Member

Sent a PR - scala/scala#6767

@eed3si9n eed3si9n self-assigned this Jun 11, 2018
@sjrd
Copy link
Member

sjrd commented Jun 11, 2018

Repeating what I said at scala/scala#6767 (comment) :

What's the rationale for this to be in Predef? It could and should be explicitly imported.

There should be much stronger resistance against putting stuff in Predef, including pimps to Any.

@SethTisue SethTisue added this to the Backlog milestone Jun 11, 2018
@eed3si9n
Copy link
Member

So it seems like people are ok with this being part of Scala library, but -1 on scala.Prefef.

Any suggestions on what the import would be called?

import scala.util.AnyOps

?

@Ichoran
Copy link

Ichoran commented Jun 11, 2018

AnyOps seems too general. Maybe ChainingOps?

@paulp
Copy link

paulp commented Jun 11, 2018

Please, you shouldn't even be considering a name like AnyOps in the standard library, ever. Don't sentence thousands of people to import renaming in every file. Every implicit in the standard library should be named something like ThisIsTheStandardLibraryAnyOps.

@eed3si9n
Copy link
Member

@paulp

Please, you shouldn't even be considering a name like AnyOps in the standard library, ever. Don't sentence thousands of people to import renaming in every file. Every implicit in the standard library should be named something like ThisIsTheStandardLibraryAnyOps.

Good point.

Maybe we should adopt the Cats naming convention and prefix the package name like scala.util.ScalaUtilChainingOps.

eed3si9n added a commit to eed3si9n/scala that referenced this issue Aug 7, 2018
Fixes scala/bug#5324

This implements an opt-in enrichment for any type called tap and pipe:

```scala
scala> import scala.util.chainingOps._
import scala.util.chainingOps._

scala> val xs = List(1, 2, 3).tap(ys => println("debug " + ys.toString))
debug List(1, 2, 3)
xs: List[Int] = List(1, 2, 3)

scala> val times6 = (_: Int) * 6
times6: Int => Int = $$Lambda$1727/1479800269@10fbbdb

scala> (1 + 2 + 3).pipe(times6)
res0: Int = 36

scala> (1 - 2 - 3).pipe(times6).pipe(scala.math.abs)
res1: Int = 24
```
@dwijnand dwijnand modified the milestones: Backlog, 2.13.0-M5 Aug 9, 2018
martijnhoekstra pushed a commit to martijnhoekstra/scala-collection-compat that referenced this issue Sep 6, 2018
Fixes scala/bug#5324

This implements an opt-in enrichment for any type called tap and pipe:

```scala
scala> import scala.util.chainingOps._
import scala.util.chainingOps._

scala> val xs = List(1, 2, 3).tap(ys => println("debug " + ys.toString))
debug List(1, 2, 3)
xs: List[Int] = List(1, 2, 3)

scala> val times6 = (_: Int) * 6
times6: Int => Int = $$Lambda$1727/1479800269@10fbbdb

scala> (1 + 2 + 3).pipe(times6)
res0: Int = 36

scala> (1 - 2 - 3).pipe(times6).pipe(scala.math.abs)
res1: Int = 24
```
@Atry
Copy link

Atry commented Mar 28, 2019

I wonder if we can backport this feature to https://github.com/scala/scala-collection-compat

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants