将无形可扩展记录传递给函数(续)

时间:2013-12-01 11:25:24

标签: scala shapeless

考虑到这个问题:Passing a Shapeless Extensible Record to a Function,Travis的答案表明,将可扩展记录作为参数的每个函数都必须有一个隐式选择器作为参数。我想知道如果我们有很多这样的功能,是否可以将这些声明分解。 例如。 :

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

由于

贝努瓦

12月10日编辑

在尝试答案的代码时,会出现两个问题:

  1. 没有告知与foo1,foo2,foo3相关的数据的真实类型:因此,像fun1这样的函数不能使用与这些类型相关联的任何方法。例如,即使foo3是Double,它也不能取其squareroot。
  2. 如果我用(“foo1” - &gt;&gt;“hello”)::(“foo2” - &gt; 1)::(“foo3” - &gt;&gt; 1.2)::来调用fun1 HNiL,结果是(hello,1,1.2)类型(selectors.s1.Out,selectors.s2.Out,selectors.s3.Out) 如果我尝试将1添加到最后一个值(1.2),Scala会抱怨它无法添加Int和selectors.s3.Out;但如果我写:

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

    我可以写:

      x._3 == 1.2
    

    和scala回答是真的!

  3. 我试图以这种方式修改代码,跳转类型将被传播,但它没有解决问题。我甚至不能用(foo1-&gt;&gt;“hello”)::(foo2 - &gt; 1)::(foo3 - &gt;&gt; 1.2):: HNil作为参数调用fun1:

    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))
     }
    

    有没有办法进步?

    贝努瓦

2 个答案:

答案 0 :(得分:14)

您可以定义自己的类型类来收集记录中包含所需字段的证据:

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]
}

然后,例如:

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

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

它仍然有点冗长,特别是因为导入是必要的,但远不如单独要求所有选择器作为隐式参数。

答案 1 :(得分:4)

可以使用以下命令指定给定字段的输出类型:

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

此外,我们可以使用类型构造函数稍微简化语法:

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)