Skip to content

Instantly share code, notes, and snippets.

@aaronlevin
Last active May 13, 2016 08:46
Show Gist options
  • Save aaronlevin/185dc5250febc67e497c55796360c9a5 to your computer and use it in GitHub Desktop.
Save aaronlevin/185dc5250febc67e497c55796360c9a5 to your computer and use it in GitHub Desktop.
Use Structured Keys in Shapeless Records
/**
* Working with shapeless' records is great. However, you are limited to using only String, Integer, Boolean, and
* (package) Objects as singleton keys as the singleton machinery requires a stable point (I don't really fully
* understand this part but I have an intuition). I had asked if there was a way to use a tuple or case-class
* as a key in the shapeless gitter channel and no one really knew.
*
* I was reading the shapeless source for the Witness type and an idea came to mind: since we can use a package Object
* as a key, what if that key extends a trait (an abstract class can work as well)? Can we use those objects as
* singleton keys and access the values specified in the trait?
*
* It turns out you can! You can also write typeclasses against these shapeless records and access the keys!
*
* Below I show how this works and give an example typeclass that will print all the keys of a shapeless record
* as it traverse the structure.
*/
object ShapelessRecordsWithArbitraryKeys {
/**
* The 'Field' trait gives structure to our shapeless singleton keys. If we wanted to use a tuple
* as a shapeless key, then we'd just add two parameters to this trait as shown below.
*/
trait Field {
val name: String
lazy val jsonName: String = name
}
// Client Id field has a different json key name
object ClientIdField extends Field {
val name = "client_id"
override lazy val jsonName = "client_identification"
}
val clientIdWitness = Witness(ClientIdField)
// Track field has a json key name the same as the field name
object TrackField extends Field {
val name = "track"
}
val trackWitness = Witness(TrackField)
// My Record type with Field as keys
type MyRecord = FieldType[clientIdWitness.T, Option[Long]] :: FieldType[trackWitness.T, Option[String]] :: HNil
// an example value of the record
val myRecord: MyRecord = ClientIdField ->> Some(123L) :: TrackField ->> Some("cool") :: HNil
// a typeclass that will crawl the record HList and print the keys
trait PrintField[-A] {
def print(s: String): Unit
}
// terminate the typeclass recursion
implicit object nil extends PrintField[HNil] {
def print(s: String): Unit = println(s)
}
// print a key
implicit def hcons[Name <: Field, H, T <: HList](
implicit
witness: Witness.Aux[Name],
tailInstance: PrintField[T]
): PrintField[FieldType[Name,H] :: T] = new PrintField[FieldType[Name,H] :: T] {
def print(s: String): Unit = {
tailInstance.print(s ++ witness.value.jsonName ++ " | ")
}
}
// small helper
def printAll[A](implicit printer: PrintField[A]) = printer.print("")
}
/**
* If you run this code in the console:
* > console
* [info] Starting scala interpreter...
* [info]
* Welcome to Scala 2.11.8 (OpenJDK 64-Bit Server VM, Java 1.8.0_76).
* Type in expressions for evaluation. Or try :help.
* > import ShapelessRecordsWithArbitraryKeys._
* import ShapelessRecordsWithArbitraryKeys._
*
* > printAll[MyRecord]
* client_identification | track |
*
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment