Cats: Kestrel combinator for Monad/Functor

Created on 14 Mar 2017  路  9Comments  路  Source: typelevel/cats

This would be extremely useful. An example use case is when using monix.Task:

val task: Task[V] = ???
task tap { v =>
  // some sort of logging
  println(v)
} // returns a Task[V]

I've found this: http://stackoverflow.com/questions/33100183/k-kestrel-combinator-for-monads -- tapM could also be useful.

Is there something that already does this in Cats? It might also make more sense in Functor. I would love to build this if there is interest in adding this to the library. Thanks!

Most helpful comment

Side effects and Functor have no relationship. You will be making an implicit assumption that Functor delays side effects.

All 9 comments

tap assumes things about the underlying Functor which are not true in the general case; I think tapM would be more useful in terms of abstraction.

@edmundnoble what does it assume? Can we not do something like

def tap(el: F[A], f: A => Unit): F[A] = {
  map(el, { a => f(a); a })
}

Side effects and Functor have no relationship. You will be making an implicit assumption that Functor delays side effects.

Is this type of thing more suited for the mouse project? https://github.com/benhutchison/mouse

@edmundnoble makes sense. It definitely should exist for Monad, though.

@stew I don't see it being part of mouse as it applies to all Monads rather than just a specific type.

It seems like this is reliant on capturing side-effects - in your example it would rely on the capture of the println. If this is true, then it would not work on Monad either. This is because there is no principled way to abstract over effect capture as effect capture is a very special operation that is data-type specific. Attempts to do so have been made in libraries like Doobie (Capture) and FS2 (Suspendable), but those are to play nice with the Scala FP ecosystem (Scalaz Task, Monix Task, FS2 Task, etc.) as opposed to generic effect capture.

@adelbertc tapM is not reliant on capturing side-effects. It is the single function of type:
def tapM[F[_]: Monad, A, U](fa: F[A])(f: A => F[U]): F[A] that doesn't ignore its argument.

Edit: to clarify, the side effects must be already included inside the F[_]. No capture is going on, at least none guaranteed by any laws.

I think this can be closed. flatTap was introduced already.

https://github.com/typelevel/cats/pull/1958

I'd personally like to see a tap as well, but I understand the hesitation there.

Was this page helpful?
0 / 5 - 0 ratings