Search code examples
scalashapeless

Shapeless: UnaryTCConstraint for Foo[T, U]


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.


Solution

  • 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.