I am trying to learn Shapeless (using version 2.10.2). I have created a very simple extensible record:
val rec1 = ("foo" ->> 42) :: HNil
According to the REPL, this has type
shapeless.::[Int with shapeless.record.KeyTag[String("foo"),Int],shapeless.HNil]
I am trying to define a simple function:
def fun(x: ::[Int with KeyTag[String("foo"), Int], HNil]) = x("foo")
but it does not even compile. I cannot use a String("foo") in the type declaration, and get an error.
I have two questions:
EDIT
I have found that:
val rec1 = ("foo" ->> 42) :: HNil
val rec2 = ("foo" ->> 43) :: HNil
var x = rec1
x = rec2
works well. I conclude rec1, rec2, and x are of the same type. I just don't know how to express that type in code!
Here's something a little more general that I think might answer your question. Suppose we want to write a method that will work on any record with a "foo"
key. We can use a combination of a witness and a selector:
import shapeless._, record._, syntax.singleton._
val w = Witness("foo")
def fun[L <: HList](xs: L)(implicit sel: ops.record.Selector[L, w.T]) = xs("foo")
And then:
scala> fun(("foo" ->> 42) :: HNil)
res0: Int = 42
Or:
scala> fun(("bar" ->> 'a) :: ("foo" ->> 42) :: HNil)
res1: Int = 42
If we really wanted to only allow records with no other fields, we could write the following:
def fun(l: Int with KeyTag[w.T, Int] :: HNil) = l("foo")
But that's somewhat at odds with the way records are generally used.
We have to define the witness precisely because Scala 2.10 doesn't provide any way to refer to a singleton type directly—see for example my fork of Alois Cochard's Shona project for some discussion.
I will add as a final disclaimer that I'm only just now getting familiar with Shapeless 2.0 myself, but I don't think even Miles is magical enough to get around this limitation.
As of shapeless 2.1.0 there is a new syntax to express record types:
scala> :paste
// Entering paste mode (ctrl-D to finish)
import shapeless._
import shapeless.record._
import shapeless.syntax.singleton._
def fun(x: Record.`"foo" -> Int`.T) = x("foo")
// Exiting paste mode, now interpreting.
import shapeless._
import shapeless.record._
import shapeless.syntax.singleton._
fun: (x: shapeless.::[Int with shapeless.labelled.KeyTag[String("foo"),Int],shapeless.HNil])Int
scala> fun( ("foo" ->> 42) :: HNil )
res2: Int = 42
scala> fun( ("foo" ->> 42) :: ("bar" ->> 43) :: HNil )
<console>:30: error: type mismatch;
found : shapeless.::[Int with shapeless.labelled.KeyTag[String("foo"),Int],shapeless.::[Int with shapeless.labelled.KeyTag[String("bar"),Int],shapeless.HNil]]
required: shapeless.::[Int with shapeless.labelled.KeyTag[String("foo"),Int],shapeless.HNil]
fun( ("foo" ->> 42) :: ("bar" ->> 43) :: HNil )
But the Selector is probably the best approach for OP's use-case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With