【问题标题】:Passing a Shapeless Extensible Record to a Function (continued)将无形可扩展记录传递给函数(续)
【发布时间】:2013-12-17 04:12:43
【问题描述】:

考虑到这个问题: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,它也不能取平方根。
  2. 如果我用 ("foo1"->> "hello") :: ("foo2" -> 1)::("foo3" ->> 1.2)::HNiL 调用 fun1,结果是 (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 的答案是真的!

我曾尝试以这种方式修改代码,希望可以传播类型,但这并不能解决问题。我什至不能用 (foo1->> "hello") :: (foo2 -> 1)::(foo3 ->> 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))
 }

有没有办法进步?

伯努瓦

【问题讨论】:

    标签: scala shapeless


    【解决方案1】:

    您可以定义自己的类型类来收集记录具有您需要的字段的证据:

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

    这仍然有点冗长,特别是因为导入是必要的,但比要求所有选择器单独作为隐式参数要少得多。

    【讨论】:

    • 在你的例子中,如果我用 ("foo1"->> "hello") :: ("foo2" -> 1)::("foo3" ->> 1.2) 调用 fun1: :HNiL,结果是 (hello, 1, 1.2) 但类型不是预期的 (String, Int, Double) 而是 (selectors.s1.Out, selectors.s2.Out, selectors.s3.Out)
    • 这些是路径相关类型,在这种情况下,它们只是StringIntDouble 的别名。编译器知道它们的真正含义,因此您应该能够按预期分配给真正的类型。
    • 我尝试:val x= ("foo1"->> "hello") :: ("foo2" -> 1)::("foo3" ->> 1.2)::HNiL 和然后:val y:Double = fun1(x)._3,但 scala 抱怨它不能将 selectors.s3.Out 分配给 Double。
    • 更新(一年半后)以展示如何使用Aux 来跟踪字段类型。
    • @TravisBrown 不应该说Selector.Aux[L, w1.T, String]等吗?
    【解决方案2】:

    给定字段的输出类型可以使用:

    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)
    

    【讨论】:

    • 太棒了!这正是我想要的!
    • 顺便说一句,如果有人到这个 SO 寻找有关使用表面上是常量键的信息传递无形记录的信息,这些键是 Java 静态决赛的别名,但遇到无形抱怨密钥不是常量或稳定值...有一个注释 @uncheckedStable 让我省了几个小时的悲伤。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-25
    • 2014-08-31
    相关资源
    最近更新 更多