I have the following working as expected:
import shapeless._
import shapeless.UnaryTCConstraint._
import shapeless.test.illTyped
case class Foo[R](result: R, dependencies: Set[Foo[_]] = Set.empty)
//This method only accepts an HList of Foo
def method[L <: HList](list: L)(implicit utcc: UnaryTCConstraint[L, Foo]) = println("checks")
val notFoos = "abc" :: 1 :: 5.5 :: HNil
illTyped { """method(notFoos)""" }
val aFoo = Foo("abc")
val bFoo = Foo(2, Set(aFoo))
val cFoo = Foo(true, Set(bFoo))
val onlyFoos = aFoo :: bFoo :: cFoo :: HNil
method(onlyFoos) // prints checks
After some refactoring I've came to the conclusion that dependencies should be a HList of Foos. So I changed the code to:
type FooWithAnyDependency[R] = Foo[R, _ <: HList]
case class Foo[R, L <: HList](result: R, dependencies: L = HNil)(implicit utcc: UnaryTCConstraint[L, FooWithAnyDependency])
def method2[L <: HList](list: L)(implicit utcc: UnaryTCConstraint[L, FooWithAnyDependency]) = println("checks")
This code compiles, however when I try to use it:
val aFoo = Foo("abc")
I get this error:
Could not find implicit value for parameter utcc: shapeless.UnaryTCConstraint[shapeless.HNil.type,FooWithAnyDependency]
Error occurred in an application involving default arguments.
val aFoo = Foo("abc")
^
I think its failing because its trying to find an UnaryTCConstraint[HNil .type, FooWithAnyDependency].
I know that implementing a custom Constraint will solve the problem (I've already done it), however I run into the same problem whenever I try to use something else, for example: Comapped.Aux[L, FooWithAnyDependency, M]
.
So the question is, how can I overcome this problem without having to re-implement most things just for Foo.
You're exactly right that the issue is the inferred HNil.type
singleton type. Fortunately the fix is very straightforward—you just provide a type annotation for HNil
:
case class Foo[R, L <: HList](
result: R,
dependencies: L = HNil: HNil
)(implicit utcc: UnaryTCConstraint[L, FooWithAnyDependency])
In general when working with Shapeless you'll need to provide a type annotation like this for HNil
to avoid the (more or less useless) singleton type HNil.type
, except when you write something like this:
val hlist = 1 :: HNil
Which will have the inferred type Int :: HNil
instead of Int :: HNil.type
only because HNil
has a ::
method that ensures that you get the right type.