Passing a Shapeless Extensible Record to a Function

ⅰ亾dé卋堺 提交于 2019-11-27 20:18:56

问题


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:

  1. How can I specify the type of the extensible record in my code?
  2. When working with records with more fields, the length and complexity of the type declaration will be unmanageable. Is there a way to create an alias for the type, given a particular instance of a record, or some other workaround?

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!


回答1:


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.




回答2:


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.



来源:https://stackoverflow.com/questions/19308143/passing-a-shapeless-extensible-record-to-a-function

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!