Reputation: 767
I'm wondering why the following code fails to compile:
package app.models.world
import java.util.UUID
import shapeless._
import shapeless.ops.record.Selector
import shapeless.record._
case class Vect2(x: Int, y: Int)
case class Bounds(position: Vect2, size: Vect2)
object WObject {
type Id = UUID
def newId: Id = UUID.randomUUID()
object Fields {
object id extends FieldOf[Id]
object position extends FieldOf[Vect2]
case class all[L <: HList](implicit
_id: Selector[L, id.type], _position: Selector[L, position.type]
)
object all {
implicit def make[L <: HList](implicit
_id: Selector[L, id.type],
_position: Selector[L, position.type]
) = all[L]
}
}
}
import WObject._
/* World object */
abstract class WObject[L <: HList](val props: L)(implicit sel: Fields.all[L]) {
def this(id: WObject.Id, position: Vect2) =
this(Fields.id ->> id :: Fields.position ->> position :: HNil)
def id = props(Fields.id)
def position = props(Fields.position)
lazy val bounds = Bounds(position, Vect2(1, 1))
}
I'm getting following errors:
[error] D:\work\scala\shapeworld\src\main\scala\app\models\WObject.scala:39: No field app.models.world.WObject.Fields.position.type in record L
[error] def position = props(Fields.position)
[error] ^
[error] D:\work\scala\shapeworld\src\main\scala\app\models\WObject.scala:36: type mismatch;
[error] found : shapeless.::[shapeless.record.FieldType[app.models.world.WObject.Fields.id.type,app.models.world.WObject.Id],shapeless.::[shapeless.record.FieldType[app.models.world.WObject.Fields.position.type,app.models.world.Vect2],shapeless.HNil]]
[error] (which expands to) shapeless.::[java.util.UUID with shapeless.record.KeyTag[app.models.world.WObject.Fields.id.type,java.util.UUID],shapeless.::[app.models.world.Vect2 with shapeless.record.KeyTag[app.models.world.WObject.Fields.position.type,app.models.world.Vect2],shapeless.HNil]]
[error] required: L
[error] this(Fields.id ->> id :: Fields.position ->> position :: HNil)
[error] ^
[error] D:\work\scala\shapeworld\src\main\scala\app\models\WObject.scala:36: could not find implicit value for parameter sel: app.models.world.WObject.Fields.all[L]
[error] this(Fields.id ->> id :: Fields.position ->> position :: HNil)
[error] ^
[error] D:\work\scala\shapeworld\src\main\scala\app\models\WObject.scala:38: No field app.models.world.WObject.Fields.id.type in record L
[error] def id = props(Fields.id)
[error] ^
[error] four errors found
[error] (compile:compile) Compilation failed
[error] Total time: 3 s, completed 2014-09-17 13.29.15
Which are weird.
1) Why does position insist that there is no field if the selector should imply that? I took my reasoning from Passing a Shapeless Extensible Record to a Function (continued)
2) Why does the secondary constructor fail?
The SBT project (56kb) can be found at https://www.dropbox.com/s/pjsn58dpqx1l4os/shapeworld.zip?dl=0
Upvotes: 2
Views: 944
Reputation: 139038
There are three small issues here. The first isn't Shapeless-specific—it's a general restriction on auxiliary constructors in Scala. You can't write the following, for example:
scala> class Foo[A](a: A) {
| def this(s1: String, s2: String) = this(s1 + s2)
| }
<console>:53: error: type mismatch;
found : String
required: A
def this(s1: String, s2: String) = this(s1 + s2)
^
I.e. if the class is generic you can't provide a constructor that's not. That's pretty easy to work around with an extra method on the companion object, though.
The second issue is that you need to track the record value types in your selectors in this case (using FieldOf
will constrain the type during creation with ->>
, but it's not going to provide the evidence you need when you're looking up a value by key). This is a pretty straightforward change:
object WObject {
type Id = UUID
def newId: Id = UUID.randomUUID()
object Fields {
object id extends FieldOf[Id]
object position extends FieldOf[Vect2]
case class all[L <: HList](implicit
_id: Selector.Aux[L, id.type, Id],
_position: Selector.Aux[L, position.type, Vect2]
)
object all {
implicit def make[L <: HList](implicit
_id: Selector.Aux[L, id.type, Id],
_position: Selector.Aux[L, position.type, Vect2]
) = all[L]
}
}
}
Lastly you need to import the contents of sel
:
abstract class WObject[L <: HList](val props: L)(implicit sel: Fields.all[L]) {
import sel._
def id: Id = props(Fields.id)
def position: Vect2 = props(Fields.position)
lazy val bounds = Bounds(position, Vect2(1, 1))
}
And that's all (except for the method replacing the auxiliary constructor, which I'll leave as an exercise for the reader).
Upvotes: 4