Search code examples
scalashapeless

Wrapping Shapeless FieldType creation in a function


Is there a way to create an implicit class to provide a custom function to return a FieldType with the same type as the record-style singleton operator ->>?

I'd like to do something like:

import shapeless.syntax.singleton._

implicit class FieldMaker[S <: Symbol](val s: S) {
  def make[T](t: T) = s ->> t
}

so that the following two values have the same type:

val first  = 'test ->> Foo("bar")
val second = 'test make Foo("bar")

In previous attempts, I keep getting thwarted by the macros in mkSingletonOps. Any advice would be helpful!

Update:

The motivation for this stems from creating a DSL and trying to carefully control its syntax. The simplified example above skips past the purpose this implicit class is accomplishing in the DSL—specifically, applying a function to T returning a typeclass which is needed elsewhere in the DSL.

A more exemplary case would be:

import shapeless.syntax.singleton._

def func(t: T): SomeTypeclass[T] = _  // elided

implicit class FieldMaker[S <: Symbol](val s: S) {
  def make[T](t: T) = s ->> func(t)
}

so that the following two values have the same type:

val first  = 'test ->> func(Foo("bar"))
val second = 'test make Foo("bar")

The expression assigned to second is the desired syntax for the DSL.


Solution

  • Probably no.

      import shapeless.Witness
      import shapeless.labelled.FieldType
      import shapeless.syntax.singleton._
    
      implicit class FieldMaker[S <: Symbol](val s: S) {
        def make[T](t: T): FieldType[s.type, T] = s ->>[T] t
      }
    
      case class Foo(s: String)
    
      val first: FieldType[Witness.`'test`.T, Foo]  = 'test ->>[Foo] Foo("bar")
      val second: FieldType[fm.s.type, Foo] forSome { val fm: FieldMaker[Symbol] } = 
        'test make[Foo] Foo("bar")
      val third: FieldType[fm.s.type, Foo] forSome { val fm: FieldMaker[Witness.`'test`.T] } = 
        'test.narrow make[Foo] Foo("bar")
    

    Type of s ->>[T] t is FieldType[s.type, T] and this doesn't depend on type parameter S. But what is s in s.type? Before implicit conversion it's 'test but after that it's just a field of an instance of FieldMaker[S] (instance, which exists only during implicit conversion) so we have existential type. And as soon as we have existential type we can't go back to 'test.

    This is how implicits work.


    Actually finally I found the way to make second of the same type as first (although it uses .narrow). I replaced implicit conversion with type class and implicit conversion.

      import shapeless.Witness
      import shapeless.labelled.FieldType
      import shapeless.syntax.singleton._
      import shapeless.tag.@@
    
      trait FieldMaker[S <: Symbol, T] {
        def make(t: T): FieldType[S, T]
      }
    
      object FieldMaker {
        implicit def mkFieldMaker[/*S <: Symbol*/U, T](implicit
                                                       witness: Witness.Aux[/*S*/Symbol @@ U]
                                                      ): FieldMaker[/*S*/Symbol @@ U, T] = {
          val name: /*S*/Symbol @@ U = witness.value
          new FieldMaker[/*S*/Symbol @@ U, T] {
            override def make(t: T): FieldType[/*S*/Symbol @@ U, T] =
              (name ->>[T] t).asInstanceOf[FieldType[/*S*/Symbol @@ U, T]]
          }
        }
    
        object op {
          implicit class FieldMakerOp[S <: Symbol](s: S) {
            def make[T](t: T)(implicit fm: FieldMaker[S, T]): FieldType[S, T] = fm.make(t)
          }
        }
    
      }
    
      case class Foo(s: String)
    
      val first: FieldType[Witness.`'test`.T, Foo]  = 'test ->>[Foo] Foo("bar")
    
      import FieldMaker.op._
      val second: FieldType[Witness.`'test`.T, Foo] = 'test.narrow make/*[Foo]*/ Foo("bar")