Last active
May 13, 2016 08:46
-
-
Save aaronlevin/185dc5250febc67e497c55796360c9a5 to your computer and use it in GitHub Desktop.
Use Structured Keys in Shapeless Records
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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