Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing a Shapeless Extensible Record to a Function (continued)

Considering this question : Passing a Shapeless Extensible Record to a Function, Travis's answer shows that every function taking an extensible record as parameter must have an implicit selector as parameter. I wonder if one could factorize those declarations in case we have many functions of this kind. E.g. :

val w1 = Witness("foo1")
val w2 = Witness("foo2")
val w3 = Witness("foo3")
//Here some "magical" declarations avoiding to declara selectors in fun1, fun2, fun3 below

 def fun1[L <: HList](xs: L) = ... //Access to foo1, foo2, foo3
 def fun2[L <: HList](xs: L) = ... //Access to foo1, foo2, foo3
 def fun3[L <: HList](xs: L) = ... //Access to foo1, foo2, foo3

Thanks

Benoit

edit on Dec 10

When trying the code of the answer, on comes with two problems :

  1. Nothing is told about the real type of the data associated to foo1, foo2, foo3 : consequently, a function like fun1 can't use any method associated to these types. e.g., even if foo3 is a Double, it can't take its squareroot.
  2. If I call fun1 with ("foo1"->> "hello") :: ("foo2" -> 1)::("foo3" ->> 1.2)::HNiL, the result is (hello, 1, 1.2) with type (selectors.s1.Out, selectors.s2.Out, selectors.s3.Out) If I try to add 1 to the last value (1.2), Scala complains that it can't add an Int and a selectors.s3.Out ;but if I write :

      val x = fun1(("foo1"->> "hello") :: ("foo2" -> 1)::("foo3" ->> 1.2)::HNil)
    

    I can write :

      x._3 == 1.2
    

    and scala answers True!

I have tried to modify the code in this way, hopping that the types would be propagated, but it doesn't solve the problem. I can't even call fun1 with (foo1->> "hello") :: (foo2 -> 1)::(foo3 ->> 1.2)::HNil as parameter :

object foo1 extends FieldOf[String]
object foo2 extends FieldOf[Int]
object foo3 extends FieldOf[Double]

val w1 = Witness(foo1)
val w2 = Witness(foo2)
val w3 = Witness(foo3)

case class HasMyFields[L <: HList](implicit
  s1: Selector[L, w1.T],
  s2: Selector[L, w2.T],
 s3: Selector[L, w3.T]
 )

 object HasMyFields {
    implicit def make[L <: HList](implicit
    s1: Selector[L, w1.T],
    s2: Selector[L, w2.T],
    s3: Selector[L, w3.T]
  ) = HasMyFields[L]
}
 def fun1[L <: HList](xs: L)(implicit selectors: HasMyFields[L]) = {
   import selectors._
   (xs(foo1), xs(foo2), xs(foo3))
 }

Is there a way to progress?

Benoit

like image 515
bhericher Avatar asked Dec 01 '13 11:12

bhericher


2 Answers

You can define your own type class to gather the evidence that the record has the fields you need:

import shapeless._, ops.record.Selector, record._, syntax.singleton._

val w1 = Witness("foo1")
val w2 = Witness("foo2")
val w3 = Witness("foo3")

case class HasMyFields[L <: HList](implicit
  s1: Selector[L, w1.T, String],
  s2: Selector[L, w2.T, Int],
  s3: Selector[L, w3.T, Double]
)

object HasMyFields {
  implicit def make[L <: HList](implicit
    s1: Selector[L, w1.T, String],
    s2: Selector[L, w2.T, Int],
    s3: Selector[L, w3.T, Double]
  ) = HasMyFields[L]
}

And then, for example:

def fun1[L <: HList](xs: L)(implicit selectors: HasMyFields[L]) = {
  import selectors._

  (xs("foo1"), xs("foo2"), xs("foo3"))
}

It's still a little verbose, especially since the import is necessary, but much less so than requiring all of the selectors individually as implicit parameters.

like image 153
Travis Brown Avatar answered Sep 30 '22 20:09

Travis Brown


The out type of a given field can be specified using:

Selector[L, w1.T] { type Out = String }

Also, we can slightly simplify the syntax using a type constructor:

import shapeless._, ops.record.Selector, record._, syntax.singleton._

val w1 = Witness("foo1")
val w2 = Witness("foo2")
val w3 = Witness("foo3")

type HasFoo1[L <: HList] = Selector[L, w1.T] { type Out = String }
type HasFoo2[L <: HList] = Selector[L, w2.T]
type HasFoo3[L <: HList] = Selector[L, w3.T]

@implicitNotFound("${L} should have foo1, foo2 and foo3")
case class HasMyFields[L <: HList](implicit s1: HasFoo1[L], s2: HasFoo2[L], s3: HasFoo3[L])

object HasMyFields {
  implicit def make[L <: HList : HasFoo1 : HasFoo2 : HasFoo3] = HasMyFields[L]
}


def fun1[L <: HList : HasMyFields](xs: L) = {
  val selectors = implicitly[HasMyFields[L]]
  import selectors._
  (xs("foo1").length, xs("foo2"), xs("foo3"))
}

fun1(("foo1"->> "hello") :: ("foo2" ->> 1)::("foo3" ->> 1.2)::HNil)

// Does not compile: the value in foo1 is not a String
fun1(("foo1"->> 2)       :: ("foo2" ->> 1)::("foo3" ->> 1.2)::HNil)
like image 39
Mikael Valot Avatar answered Sep 30 '22 19:09

Mikael Valot