If you want to know more about Datomisca/Datomic schema go to my recent article. What’s interesting with Datomisca schema is that they are statically typed allowing some compiler validations and type inference.
HList are able to contain different types of data and able to keep tracks of these types.
This project is an experience trying to :
convert HList to/from Datomic Entities
check HList types against schema at compile-time
This uses :
Datomisca type-safe schema
Shapeless HList
Shapeless polymorphic functions
Please note that we don’t provide any Iso[From, To] since there is no isomorphism here.
Actually, there are 2 monomorphisms (injective):
HList => AddEntity to provision an entity
DEntity => HList when retrieving entity
We would need to implement Mono[From, To] certainly for our case…
Code sample
Create schema based on HList
123456789101112131415161718192021
// Koala SchemaobjectKoala{objectns{valkoala=Namespace("koala")}// schema attributesvalname=Attribute(ns.koala/"name",SchemaType.string,Cardinality.one).withDoc("Koala's name")valage=Attribute(ns.koala/"age",SchemaType.long,Cardinality.one).withDoc("Koala's age")valtrees=Attribute(ns.koala/"trees",SchemaType.string,Cardinality.many).withDoc("Koala's trees")// the schema in HList formvalschema=name::age::trees::HNil// the datomic facts corresponding to schema // (need specifying upper type for shapeless conversion to list)valtxData=schema.toList[Operation]}// Provision schemaDatomic.transact(Koala.txData)map{tx=>...}
Validate HList against Schema
1234567891011121314151617181920212223
// creates a Temporary ID & keeps it for resolving entity after insertionvalid=DId(Partition.USER)// creates an HList entity valhListEntity=id::"kaylee"::3L::Set("manna_gum","tallowwood")::HNil// validates and converts at compile-time this HList against schemahListEntity.toAddEntity(Koala.schema)// If you remove a field from HList and try again, the compiler failsvalbadHListEntity=id::"kaylee"::Set("manna_gum","tallowwood")::HNilscala>badHListEntity.toAddEntity(Koala.schema)<console>:23:error:couldnotfindimplicitvalueforparameterpull:shapotomic.SchemaCheckerFromHList.Pullback2[shapeless.::[datomisca.TempId,shapeless.::[String,shapeless.::[scala.collection.immutable.Set[String],shapeless.HNil]]],
shapeless.::[datomisca.RawAttribute[datomisca.DString,datomisca.CardinalityOne.type],
shapeless.::[datomisca.RawAttribute[datomisca.DLong,datomisca.CardinalityOne.type],
shapeless.::[datomisca.RawAttribute[datomisca.DString,datomisca.CardinalityMany.type],shapeless.HNil]]],datomisca.AddEntity]
The compiler error is a bit weird at first but if you take a few seconds to read it, you’ll see that there is nothing hard about it, it just says:
Convert DEntity to static-typed HList based on schema
1234567
vale=Datomic.resolveEntity(tx,id)// rebuilds HList entity from DEntity statically typed by schemavalpostHListEntity=e.toHList(Koala.schema)// Explicitly typing the value to show that the compiler builds the right typed HList from schemavalvalidateHListEntityType:Long::String::Long::Set[String]::HNil=postHListEntity
Conclusion
Using HList with compile-time schema validation is quite interesting because it provides a very basic and versatile data structure to manipulate Datomic entities in a type-safe style.
Moreover, as Datomic pushes atomic data manipulation (simple facts instead of full entities), it’s really cool to use HList instead of rigid static structure such as case-class.