Search code examples
scalashapeless

use implicit to find "One" element of HList typeclass to inject


Here is the code:

    trait Service[T<: HList] {
      def doStuff(): Unit
    }

    class A
    class B
    class C

    class ServiceAB extends Service[A :: B :: HNil] {
      override def doStuff(): Unit = println("handling a b")
    }

    class ServiceC extends Service[C :: HNil] {
      override def doStuff(): Unit = println("handling c")
    }

    implicit val serviceAB = new ServiceAB
    implicit val serviceC = new ServiceC

    def operate[T, W <: HList](x: T)(implicit service: Service[W]) = {
      service.doStuff()
    }

    operate(new C)

I just wonder is it possible or what should I code in the type level to inject implicit serviceC when operate(new C) is executed, since class C is the element of HList of type class of ServiceC ?

Many thanks in advance


Solution

  • I realy don't know why you need this :)

    So your code works, but if you pass type parameter explicitly:

    operate[C, C :: HNil](new C)
    

    If you want same, but implicitly, you can define your class type:

    trait Service[L <: HList, U] { def doStuff(): Unit }
    
    trait lowPriority {
      implicit def otherwise[L <: HList, U] =
        new Service[L, U] {
          def doStuff(): Unit = println("handling otherwise")
        }
    }
    
    object Service extends lowPriority {
      implicit def ab[L <: HList, U]
      (implicit e: L =:= (A :: B :: HNil), 
                s: Selector[L, U]) =
        new Service[L, U] {
          def doStuff(): Unit = println("handling a b")
        }
    
      implicit def c[L <: HList, U]
      (implicit e: L =:= (C :: HNil), 
                s: Selector[L, U]) =
        new Service[L, U] {
          def doStuff(): Unit = println("handling c")
        }
      }
    }
    
    def operate[T, W <: HList](x: T)(implicit service: Service[W, T]) = {
      service.doStuff()
    }
    

    So this works as expected:

    operate(new C) //> handling c
    operate(new A) //> handling a b
    operate(new B) //> handling a b
    

    It is possible to make it more general (so it will check is type you need is in a HList, ohterwise if it doesn't) (using Curry-Howard isomorphism, great article with explanations by Miles Sabin: http://www.chuusai.com/2011/06/09/scala-union-types-curry-howard/):

    import reflect.runtime.universe._
    
    type ¬[A] = A => Nothing
    type ∨[T, U] = ¬[¬[T] with ¬[U]]
    type ¬¬[A] = ¬[¬[A]]
    
    class A
    class B
    class C
    class D //> additional class for example
    
    trait Service[L <: HList, U] { def doStuff(): Unit }
    
    trait lowPriority {
      implicit def otherwise[L <: HList, U] =
        new Service[L, U] {
          def doStuff(): Unit = println("handling otherwise")
        }
    }
    
    object Service extends lowPriority {
      implicit def ab[L <: HList, U]
      (implicit e: (¬¬[U] <:< (A ∨ B)), 
                s: Selector[L, TypeTag[U]]) =
        new Service[L, U] {
          def doStuff(): Unit = println("handling a b")
        }
    
      implicit def c[L <: HList, U](implicit e: U =:= C, s: Selector[L, TypeTag[U]]) =
        new Service[L, U] {
          def doStuff(): Unit = println("handling c")
        }
      }
    }
    
    def operateBi[T, W <: HList](x: T, w: W)(implicit service: Service[W, T]) = {
      service.doStuff()
    }
    

    Defining HLists of types:

    val hl1 = implicitly[TypeTag[A]] :: implicitly[TypeTag[B]] :: HNil
    val hl2 = implicitly[TypeTag[C]] :: HNil
    
    operateBi(new C, hl1)
    operateBi(new A, hl2)
    operateBi(new B, hl1)
    operateBi(new D, hl1)
    

    Works as expected.