Cats: Typeclasses do not play well with type aliases

Created on 23 Jul 2018  Â·  5Comments  Â·  Source: typelevel/cats

It seems Scala fails to find correct implicits when using type aliases:

case class MyData[A]()

implicit val myFunctor: Functor[MyData] = new Functor[MyData] {
  override def map[A, B](fa: MyData[A])(f: A => B): MyData[B] = MyData[B]()
}

def showOption[A](opt: Option[A]): String = opt match {
  case Some(a) => a.toString
  case None => "nothing"
}

type T[A] = MyData[Option[A]]

val a: MyData[Option[Int]] = MyData()
val b: MyData[String] = a map showOption <-- compiles
val c: T[Int] = MyData()
val d: MyData[String] = c map showOption  <-- compilation error

Similar code written in Haskell compiles fine:

{-# LANGUAGE ScopedTypeVariables #-}

data MyData a = MyData

instance Functor MyData where
  fmap _ _ = MyData

showMaybe :: (Show a) => Maybe a -> String
showMaybe (Just a) = show a
showMaybe Nothing  = "nothing"

type T a = MyData (Maybe a)

main :: IO ()
main =
  let (a :: MyData (Maybe Int)) = MyData
      (b :: MyData String) = fmap showMaybe a
      (c :: T Int) = MyData
      (d :: MyData String) = fmap  showMaybe c
  in return ()

Most helpful comment

As far as I know the only general solution is "don't do that".

"Don't do that" or "always go through some abstraction F[_]". I generally find myself structuring my code according to the latter.

All 5 comments

This is a known issue with dealiasing. Sometimes scalac just isn't particularly good about throwing away the hidden identity of the type alias and that can result in situations like this. In practice, this usually isn't a problem since you're often going through abstractions (e.g. def foo[F[_]: Functor]) which will force the dealiasing by definition, but sometimes you can see it. State/StateT/IndexedStateT can exhibit this issue on occasion.

Is there any workaround? I have several nested types in my project, and type signatures mind be much nicer if i'll be able to use type aliases.

Type aliases aren't as innocent as they look … in this case you're changing the shape from F[G[?]] to F[?], which is significant in Scala. In fact for several years we relied on this behavior to work around legendary issue SI-2712.

As far as I know the only general solution is "don't do that".

As far as I know the only general solution is "don't do that".

"Don't do that" or "always go through some abstraction F[_]". I generally find myself structuring my code according to the latter.

What do you mean by going through some abstraction? Е.g. having function foo[T]: MyData[Option[T]]how it could be modified to use shorter abbreviation for MyData[Option[T]] other than by using type alias?

Was this page helpful?
0 / 5 - 0 ratings