Skip to content

Instantly share code, notes, and snippets.

@mergeconflict
Created March 24, 2012 22:24
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mergeconflict/2188596 to your computer and use it in GitHub Desktop.
Save mergeconflict/2188596 to your computer and use it in GitHub Desktop.
packaging and unpackaging multiple implicit conversions in an HList
object AllExamples extends App {
import shapeless._
final class All[L <: HList](val values: L) {
def apply[A](implicit selector: Selector[L, A]): A = values.select[A]
}
object All {
// package a value of type A, convertible to type B, into an HList containing just B
implicit def toAll0[A, B](a: A)(implicit a2b: A => B): All[B :: HNil] =
new All(a :: HNil)
// package a value of type A, convertible to B and some other types L, into an HList containing B and all types in L
implicit def toAll1[A, B, L <: _ :: _](a: A)(implicit a2b: A => B, a2l: A => All[L]): All[B :: L] =
new All(a :: a.values)
}
// example data
trait A { def a: String }
trait B { def b: String }
trait C { def c: String }
implicit def intA(n: Int): A = new A { def a = "a " + n.toString }
implicit def intB(n: Int): B = new B { def b = "b " + n.toString }
implicit def intC(n: Int): C = new C { def c = "c " + n.toString }
implicit def stringA(s: String): A = new A { def a = "a " + s }
implicit def stringB(s: String): B = new B { def b = "b " + s }
implicit def stringC(s: String): C = new C { def c = "c " + s }
// implicitly convert int and string values to A, B and Cs
val abcs: Seq[All[A :: B :: C :: HNil]] = Seq(1, "haha", 2, "hoho")
// unpack
for (abc <- abcs) {
println(abc[A].a)
println(abc[B].b)
println(abc[C].c)
}
}
@milessabin
Copy link

I've tried a few variations, but I can't see any obvious way to significantly improve on this. Nice work :-)

@mergeconflict
Copy link
Author

Thanks :) Did you figure out some explanation for why I must explicitly annotate the apply[A] method's type in abc[A].a, etc.? I assume it's something to do with the direction in which type inference flows, but I don't have a more formal understanding than that...

@mergeconflict
Copy link
Author

Also BTW, I think I've figured out a way to sugar All[A :: B :: C :: HNil] into the syntax I'd wanted before, A & B & C. I could submit a pull request with all this stuff, to add to your examples, if you're interested...

@milessabin
Copy link

Yes. The problem is that All.apply[T] is only applicable as a conversion from abc to A once we have inferred that T is A. But the compiler will only attempt that inference if it thinks that All.apply[T] is applicable. Chicken, meet egg. Egg, chicken. :-(

@milessabin
Copy link

That'd be great :-)

A couple of things first ... could you put an Apache 2 license header at the top (copy one from one of the other shapeless files). Also, I think you can change "L <: _ :: _" @ line 15 to "L <: HList". I might make a few minor tweaks once it's in.

@mergeconflict
Copy link
Author

Hmmm, I'm not sure I understand your explanation about conversions exactly. I didn't think there were any actual conversions taking place at the time when we're trying to unpack All. Did you mean that it can't resolve the implicit selector: Selector[L, A] evidence until it's inferred a binding for A, and it can't infer a binding for A until it's resolved the implicit?

I noticed that the way you solved that chicken/egg problem in your Pack example was to provide unpack as an implicit member which you import at the use site. This works I guess because there's only one typeclass there (i.e. the F in Pack[F[_], L <: HList])? That is, if you wanted to pack an arbitrary number of typeclass instances you wouldn't be able to do the same trick, right?

@milessabin
Copy link

Yes, exactly that. Sorry I confused things by mentioning conversions ... I was playing around with something which tried to use a conversion from All[... T ...] to T which got bitten by exactly the same problem.

In my Pack case things are a bit different: the additional structure in the result type (due to the outer type constructor) seems to be enough to allow the inference/resolution to go through. I think having a single argument list with multiple Packs at different type constructors will probably work, but I haven't checked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment