Reactormonk Reactormonk - 2 months ago 7
Scala Question

map HList with default

http://scastie.org/22713

Local copy:

/***
scalaVersion := "2.11.8"
addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.7.1")
libraryDependencies ++= {
val shapelessVersion = "2.2.5"
Seq(
"com.chuusai" %% "shapeless" % shapelessVersion
)
}
*/

import shapeless._

case class Foo()

trait Wrapper

case class S(s: String) extends Wrapper
case class I(i: Int) extends Wrapper

object Main extends App {
object wrap extends Poly1 {
implicit def caseString = at[String](S.apply)
implicit def caseInt = at[Int](I.apply)
implicit def caseOther[T] = at[T](identity)
}

type A = Foo :: String :: HNil
type B = Foo :: Int :: HNil
type Out = Foo :: Wrapper :: HNil

val a: A = Foo() :: "foo" :: HNil
val b: B = Foo() :: 42 :: HNil

val aw: Out = a.map(wrap)
val bw: Out = b.map(wrap)

}


Errors:

[error] /tmp/renderercqsCBmArxo/src/main/scala/test.scala:56: could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper[Main.wrap.type,Main.A]
[error] val aw: Out = a.map(wrap)
[error] ^
[error] /tmp/renderercqsCBmArxo/src/main/scala/test.scala:57: could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper[Main.wrap.type,Main.B]
[error] val bw: Out = b.map(wrap)
[error] ^
[error] two errors found
[error] (compile:compileIncremental) Compilation failed


How would I change that single last element into another one?

Answer

Shapeless's Poly is really just a way of bundling some implicit instances that describe what should happen to different types, so you can use the same implicit prioritization tricks you'd use in other situations in Scala:

object wrap extends LowPriorityWrapCases {
  implicit val caseString = at[String](S.apply)
  implicit val caseInt = at[Int](I.apply)
}

trait LowPriorityWrapCases extends Poly1 {
  implicit def caseOther[T] = at[T](identity)
}

This will result in implicit search checking the specific cases first and only then falling through to the default case, instead of simply throwing its hands up because of ambiguity if the hlist has a String or Int element.

As a side note, I'd suggest providing explicit type annotations here:

object wrap extends LowPriorityWrapCases {
  implicit val caseString: Case.Aux[String, S] = at[String](S.apply)
  implicit val caseInt: Case.Aux[Int, I] = at[Int](I.apply)
}

trait LowPriorityWrapCases extends Poly1 {
  implicit def caseOther[T]: Case.Aux[T, T] = at[T](identity)
}

It's a little more noisy, but it can save you from some annoying and difficult to debug issues.

Comments