1

I am trying to do something with monads in scala using scalaz library, and have some trouble making it work nicely with subtyping.

I have started with defining my own monad. Let it be an identity monad for the sake of simplicity:

import scalaz._
import Scalaz._

class Id[+A] (val value : A) { }

implicit object IdMonad extends Monad[Id] {
    override def pure[A](a : => A) = new Id(a)
    override def bind[A, B](a : Id[A], f : A => Id[B]) = f(a.value)
}

Next, I have extended it with some additional functionality:

class ExtendedId[A] (value : A, val printer : A => String) extends Id[A](value) { }

With this additional functionality, ExtendedId is not a monad anymore.

Now I want to use the object of type ExtendedId[A] as an Id[A]:

def increment1(v : ExtendedId[Int]) : Id[Int] = {
    for(v <- v) yield v + 1;
    //    ^
    //  error: could not find implicit value for parameter t:  scalaz.Functor[test.package.ExtendedId]
}

Note that I understand that since ExtendedId is not a monad, the best I can get as an output is Id[Int], and I am okay with that! But unfortunately, that code still does not compile.

However, this one does:

def asId[A](a : ExtendedId[A]) : Id[A] = a

def increment2(v : ExtendedId[Int]) {
    for(v <- asId(v)) yield v + 1;
}

Here, asId function does nothing more than upcasting its argument to from ExtendedId[A] to Id[A]. It seems that it should be completely redundant, but it is not.

Why is this happening? There does exist an implicit conversion from Id[A] to an object containing map and there obviously does exist a trivial implicit conversion from ExtendedId[A] to Id[A]. So, why is the compiler unable combine them?

Rotsor
  • 13,655
  • 6
  • 43
  • 57

1 Answers1

0

This happens because Scalaz does not define Monad as being covariant in its first type argument (or, more precisely, type constructor argument). In other words, a Monad[A] is considered a different type altogether from a Monad[B], even if A <: B. (More on covariance and contravariance)

There are good reasons why Monad is invariant. One is: if you make the compiler believe that a Monad[Id] is actually also valid as a Monad[ExtendedId], you're bound to run into problems at some point — one of them is that wherever pure is called, the compiler will infer an ExtendedId result type, whereas only an Id would be returned.

I think there isn't a technique to fix this cleanly — other than defining a Monad[ExtendedId], or something like

implicit def idMonad[A[_] <: Id[_]]: Monad[A] = ...

which indeed is able to return a proper monad for all subclasses of Id.

Community
  • 1
  • 1
Jean-Philippe Pellet
  • 59,296
  • 21
  • 173
  • 234
  • I can not define a `Monad[ExtendedId]` because `ExtendedId` is not a monad -- there is no useful implementation of `bind` for `ExtendedId` (`printer` value makes it impossible). What I can do is to cast `ExtendedId[A]` to `Id[A]`, but I don't understand why the compiler does not do that implicitly. I mean, upcasting is the mother of all implicit conversions and still it is not performed implicitly. – Rotsor May 23 '11 at 10:56
  • The notation `[A <: Id]` seems to be invalid because the `Id` type constructor needs parameters. And `[A[_] <: Id[_]]` complains about some "illegal cyclic reference involving type A". – Rotsor May 23 '11 at 11:02
  • @Rostor You're right, I had messed up my type parameters. I've edited my answer. As I wrote, the compiler doesn't cast things the way you describe because `Monad` is not covariant; i.e., the definition of `Monad` explicitly says, in practice, “a `Monad[A]` is not a `Monad[B]` even if `B <: A`.” If you explicitly cast, you're telling the compiler that you know it's safe for you — but it's not in the general case of Scalaz monads. – Jean-Philippe Pellet May 23 '11 at 11:36
  • "a `Monad[A]` is not a `Monad[B]`" -- I am fine with that. I don't want `Monad[B]`. What I want is an upcast (which is always safe and normally implicit) from B[V] to A[V] before even trying to obtain a `Monad` instance. – Rotsor May 23 '11 at 12:31
  • The code `idMonad[A <: Id[_]]: Monad[A]` still can't compile because this way `A` is not a type constructor, but `Monad` requires one. I've managed to compile [A[X] <: Id[X]], but with scala 2.7.7 (not too recent, I know) a function defined like this crashes in runtime, indicating a bug in the compiler. – Rotsor May 23 '11 at 12:32
  • @Rostor Darn, I should pay better attention to what I write. It compiles fine now (tested it on the REPL v2.8.1, didn't get your “illegal cyclic reference involving type A”). – Jean-Philippe Pellet May 23 '11 at 12:40