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.
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")