Can one abstract over monad transformers?

110 views
Skip to first unread message

Chris Marshall

unread,
Jan 30, 2014, 3:37:01 AM1/30/14
to sca...@googlegroups.com
I have some type:

   scala> trait MyStuff { def foobar: String }
   defined trait MyStuff

If I'm accessing this commonly via the IO monad, I might wish, for reasons of readability to decorate it as follows:

   scala> implicit class IOOfMyStuff(iom: IO[MyStuff]) { def foobar: IO[String] = iom map (_.foobar) }
   defined class IOOfMyStuff

Suppose I commonly decorate my IO access via various monad transformers (OptionT, StreamT, EitherT etc), and I wish to provide a "more readable" foobar, I might do this:

  scala> implicit class OptionTIOOfMyStuff(iom: OptionT[IO, MyStuff]) { 
              | def foobar: OptionT[IO, String] = iom map (_.foobar) 
              | }
  defined class OptionTIOOfMyStuff

But then I want this to work for any monad transformer, I declare more instances...

  scala> implicit class EitherTTIOOfMyStuff(iom: EitherT[IO, Throwable, MyStuff]) { 
              | def foobar: EitherT[IO, Throwable, String] = iom map (_.foobar) 
              | }
  defined class EitherTTIOOfMyStuff

Is there a way of abstracting over the monad transformer here?

Cheers, Chris



Kenji Yoshida

unread,
Jan 30, 2014, 5:13:38 AM1/30/14
to sca...@googlegroups.com

trait MyStuff { def foobar: String }
 
object Main{
import scalaz._,Scalaz._
import effect.IO
 
final class MyFunctorOps[F[_], A](self: F[A], F: Functor[F]){
def foobar(implicit A: A =:= MyStuff): F[String] =
F.map(self)(_.foobar)
}
 
implicit def myFunctorOps[FA](fa: FA)(implicit F: Unapply[Functor, FA]) =
new MyFunctorOps[F.M, F.A](F(fa), F.TC)
 
val a = OptionT[IO, MyStuff](IO(None))
val b = EitherT[IO, Throwable, MyStuff](IO(-\/(new Error)))
 
a.foobar
b.foobar
}


Chris Marshall

unread,
Jan 30, 2014, 6:51:32 AM1/30/14
to sca...@googlegroups.com
Thanks, that's really rather amazingly cool. Do you mind if I use this in a talk I'm giving in March?

In reality my problem is more complicated because I have the following

  trait Ev
  trait SubEv1 extends Ev
  trait SubEv2 extends Ev

and the "MyStuff" is going to be a List[SubEv1] or a List[SubEv2]. Essentially I have this helper method:

  scala> object EvHelpers { def mapped[T <: Ev](evs: Traversable[T]): Map[Int, T] = ??? }
  defined module EvHelpers

And I want to decorate any F[CC[T]] for F: Functor, CC <: traversable and T <: Ev, with a "mapped" method. So I have to pass a type T thru:

  scala> final class TraversableEvFunctorOps[F[_], A, T <: Ev](self: F[A], F: Functor[F]) {
     |   def mapped(implicit A: A => Traversable[T]): F[Map[Int, T]] = F.map(self)(a => EvHelpers.mapped(A(a)))
     | }
  defined class TraversableEvFunctorOps

  scala> implicit def traversableEvFunctorOps[FA, T <: Ev](fa: FA)(implicit F: Unapply[Functor, FA]) = new TraversableEvFunctorOps[F.M, F.A, T](F(fa), F.TC)
  traversableEvFunctorOps: [FA, T <: Ev](fa: FA)(implicit F: scalaz.Unapply[scalaz.Functor,FA])TraversableEvFunctorOps[F.M,F.A,T]

But scala doesn;t seem to be able to infer T here

scala> some(List(new SubEv1 {} ))
res11: Option[List[SubEv1]] = Some(List($anon$1@296e2712))

scala> lazy val x = res11.mapped
<console>:19: error: No implicit view available from List[SubEv1] => Traversable[T].
       lazy val x = res11.mapped
                          ^

I've also tried using implicit conforms on T:

scala> object EvHelpers { def mapped[T](evs: Traversable[T])(implicit t: T <:< Ev): Map[Int, T] = ??? }
defined module EvHelpers

scala> final class TraversableEvFunctorOps[F[_], A, T](self: F[A], F: Functor[F])(implicit t: T <:< Ev) {
     |   def mapped(implicit A: A => Traversable[T]): F[Map[Int, T]] = F.map(self)(a => EvHelpers.mapped[T](A(a)))
     | }









--
You received this message because you are subscribed to the Google Groups "scalaz" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scalaz+un...@googlegroups.com.
To post to this group, send email to sca...@googlegroups.com.
Visit this group at http://groups.google.com/group/scalaz.
For more options, visit https://groups.google.com/groups/opt_out.

Chris Marshall

unread,
Jan 30, 2014, 6:52:46 AM1/30/14
to sca...@googlegroups.com
..sent too soon

  scala> lazy val x = res11.mapped
  <console>:23: error: Cannot prove that T <:< Ev.
       lazy val x = res11.mapped
                   ^
  <console>:23: error: value mapped is not a member of Option[List[SubEv1]]
       lazy val x = res11.mapped
                          ^

Anyone have any ideas how I can get this to work? (I admit it seems rather ambitious)

Chris


Stephen Compall

unread,
Jan 31, 2014, 10:14:05 PM1/31/14
to sca...@googlegroups.com
On Thu, 2014-01-30 at 11:52 +0000, Chris Marshall wrote:

  scala> lazy val x = res11.mapped
  <console>:23: error: Cannot prove that T <:< Ev.
       lazy val x = res11.mapped
                   ^
  <console>:23: error: value mapped is not a member of Option[List[SubEv1]]
       lazy val x = res11.mapped
                          ^


Anyone have any ideas how I can get this to work? (I admit it seems rather ambitious)

Your latter approach is close; define the ops class as follows:

final class TraversableEvFunctorOps[F[_], A](self: F[A], F: Functor[F]) {
  def mapped[T <: Ev](implicit A: A <:< Traversable[T]): F[Map[Int, T]] = F.map(self)(a => EvHelpers.mapped(a))
}

Note the declaration position of T.  Fix up the implicit conversion and you're golden.
-- 
Stephen Compall
"^aCollection allSatisfy: [:each|aCondition]": less is better

Chris Marshall

unread,
Feb 3, 2014, 4:15:05 AM2/3/14
to sca...@googlegroups.com
Thanks - pretty amazing that you can abstract over 3 independent things at the same time!


Reply all
Reply to author
Forward
0 new messages