Skip to content

Instantly share code, notes, and snippets.

@aposwolsky
Created March 18, 2013 05:50
Show Gist options
  • Save aposwolsky/5185293 to your computer and use it in GitHub Desktop.
Save aposwolsky/5185293 to your computer and use it in GitHub Desktop.
type-safe priming - generalized. Example: Checkin has a (1) Venue and (2) User foreign key. Venue in turn has an (1) Owner and (2) Page foreign key. This example shows how to take a list of checkins and prime all the fields in a type-safe manner. In the end it will call a print routine that requires a list of checkins that is fully primed and wh…
package demov5
/* **** GENERAL PRIMEABLE FIELDS **** */
class PrimedReference[T] {
private var _obj: Option[T] = None
private var _isCalced = false
def cachedObj: Option[T] = {
if (!_isCalced) {
throw new RuntimeException("Attempting to access unprimed resource!... impossible!")
}
_obj
}
def isCalced: Boolean = _isCalced
def primeObj(obj: Option[T]) = {
_isCalced = true
_obj = obj
}
}
class TypedFK[BaseT, PrivateRealT, HasTPrimed[_ <: BaseT]](val getForeignId: (Unit => Int)) {
def id: Int = getForeignId()
val ref = new PrimedReference[PrivateRealT]
}
trait MetaRecord[T, PrivateRealT <: T] {
def fetchById(id: Int): T
}
/* ******* VENUE MODEL ******* */
sealed abstract class Venue {
def venueId: Int
def ownerId: Int
def pageId: Int
}
trait HasVenuePrimed[+VenueT <: Venue] { def venue: Option[VenueT] }
trait HasVenueFK extends HasVenuePrimed[PrivateRealVenue] {
val venueId: Int
val venueFk = new TypedFK[Venue, PrivateRealVenue, HasVenuePrimed](Unit => venueId)
def venue: Option[PrivateRealVenue] = venueFk.ref.cachedObj
}
abstract class PrivateRealVenue extends Venue with HasOwnerFK with HasPageFK
object MetaVenue extends MetaRecord[Venue, PrivateRealVenue] {
def createRecord(vId: Int, oId: Int, pId: Int): Venue = {
new PrivateRealVenue {
val venueId: Int = vId
val ownerId: Int = oId
val pageId: Int = pId
}
}
def fetchById(id: Int): Venue = createRecord(id, id + 100, id + 200)
}
/* ******* CHECKIN MODEL ******* */
sealed abstract class Checkin {
def checkinId: Int
def venueId: Int
def userId: Int
}
abstract class PrivateRealCheckin extends Checkin with HasVenueFK with HasUserFK
object MetaCheckin extends MetaRecord[Checkin, PrivateRealCheckin] {
def createRecord(cId: Int, vId: Int, uId: Int): Checkin = {
new PrivateRealCheckin {
val checkinId: Int = cId
val venueId: Int = vId
val userId: Int = uId
}
}
def fetchById(id: Int): Checkin = throw new RuntimeException("cannot fetch checkins")
}
/* ******* USER MODEL ******* */
sealed abstract class User {
val userId: Int
def name: String
}
abstract class PrivateRealUser extends User { def name = "User" + userId }
trait HasUserPrimed[+UserT <: User] { def user: Option[UserT] }
trait HasUserFK extends HasUserPrimed[PrivateRealUser]{
val userId: Int
val userFk = new TypedFK[User, PrivateRealUser, HasUserPrimed](Unit => userId)
def user: Option[PrivateRealUser] = userFk.ref.cachedObj
}
object MetaUser extends MetaRecord[User, PrivateRealUser]{
def createRecord(id: Int): User = new PrivateRealUser {val userId: Int = id}
def fetchById(id: Int): User = createRecord(id)
}
/* ******* OWNER MODEL ******* */
sealed abstract class Owner {
val ownerId: Int
def name: String
}
abstract class PrivateRealOwner extends Owner { def name = "Owner" + ownerId }
trait HasOwnerPrimed[+OwnerT <: Owner] { def owner: Option[OwnerT] }
trait HasOwnerFK extends HasOwnerPrimed[PrivateRealOwner]{
val ownerId: Int
val ownerFk = new TypedFK[Owner, PrivateRealOwner, HasOwnerPrimed](Unit => ownerId)
def owner: Option[PrivateRealOwner] = ownerFk.ref.cachedObj
}
object MetaOwner extends MetaRecord[Owner, PrivateRealOwner] {
def createRecord(id: Int): Owner = new PrivateRealOwner {val ownerId: Int = id}
def fetchById(id: Int): Owner = createRecord(id)
}
/* ******* PAGE MODEL ******* */
sealed abstract class Page {
val pageId: Int
def name: String
}
abstract class PrivateRealPage extends Page { def name = "Page" + pageId }
trait HasPagePrimed[+PageT <: Page] { def page: Option[PageT] }
trait HasPageFK extends HasPagePrimed[PrivateRealPage]{
val pageId: Int
val pageFk = new TypedFK[Page, PrivateRealPage, HasPagePrimed](Unit => pageId)
def page: Option[PrivateRealPage] = pageFk.ref.cachedObj
}
object MetaPage extends MetaRecord[Page, PrivateRealPage]{
def createRecord(id: Int): Page = new PrivateRealPage {val pageId: Int = id}
def fetchById(id: Int): Page = createRecord(id)
}
/* ******** PRIMING LIBRARY ********* */
class PrimingService {
def prime[RecordT, PrivateRealRecordT,
ReferencedBaseT, NewFK[_ <: ReferencedBaseT], PrivateRealReferenceT <: ReferencedBaseT]
(records: List[RecordT],
recordMeta: MetaRecord[_, PrivateRealRecordT],
referencedMeta: MetaRecord[ReferencedBaseT, PrivateRealReferenceT])
(getTypedFK: (PrivateRealRecordT => TypedFK[ReferencedBaseT, PrivateRealReferenceT, NewFK])) : List[RecordT with NewFK[ReferencedBaseT]] = {
val realRecords = records.asInstanceOf[List[PrivateRealRecordT]]
realRecords.foreach(record => {
val typedFK: TypedFK[ReferencedBaseT, PrivateRealReferenceT, NewFK] = getTypedFK(record)
val realObject = referencedMeta.fetchById(typedFK.id).asInstanceOf[PrivateRealReferenceT]
typedFK.ref.primeObj(Some(realObject))
})
records.asInstanceOf[List[RecordT with NewFK[ReferencedBaseT]]]
}
def innerPrime[BaseRecordT, PrivateRealRecordT <: BaseRecordT, ReferencedBaseT,
ReferencedT <: ReferencedBaseT, PrivateRealReferenceT <: ReferencedBaseT,
ReferencedTransformedT <: ReferencedBaseT,
FK[_ <: ReferencedBaseT], FKS]
(records: List[BaseRecordT with FK[ReferencedT] with FKS],
recordMeta: MetaRecord[BaseRecordT, PrivateRealRecordT],
referencedMeta: MetaRecord[ReferencedBaseT, PrivateRealReferenceT])
(getTypedFK: (PrivateRealRecordT => TypedFK[ReferencedBaseT, PrivateRealReferenceT, FK]),
transformer: (List[ReferencedT] => List[ReferencedTransformedT])): List[BaseRecordT with FK[ReferencedTransformedT] with FKS] = {
val realRecords = records.asInstanceOf[List[PrivateRealRecordT]]
val innerRecords = realRecords.flatMap(record => getTypedFK(record).ref.cachedObj).asInstanceOf[List[ReferencedT]]
transformer(innerRecords)
realRecords.asInstanceOf[List[BaseRecordT with FK[ReferencedTransformedT] with FKS]]
}
}
object Priming {
val primer = new PrimingService
class VenueList[VenueT <: Venue](xs: List[VenueT]) {
def primeOwner: List[VenueT with HasOwnerPrimed[Owner]] = primer.prime(xs, MetaVenue, MetaOwner)(_.ownerFk)
def primePage: List[VenueT with HasPagePrimed[Page]] = primer.prime(xs, MetaVenue, MetaPage)(_.pageFk)
}
class CheckinList[T <: Checkin](xs: List[T]) {
def primeVenue: List[T with HasVenuePrimed[Venue]] = primer.prime(xs, MetaCheckin, MetaVenue)(_.venueFk)
def primeUser: List[T with HasUserPrimed[User]] = primer.prime(xs, MetaCheckin, MetaUser)(_.userFk)
}
class CheckinPrimedVenueList[FKS, VenueT <: Venue](xs: List[Checkin with HasVenuePrimed[VenueT] with FKS]) {
def primeInsideVenue[VenueTransformed <: VenueT](transformer: (List[VenueT] => List[VenueTransformed])) =
primer.innerPrime(xs, MetaCheckin, MetaVenue)(_.venueFk, transformer)
}
/***** IMPLICITS FOR OUTER PRIMING *** */
implicit def wrapVenueList[T <: Venue](xs: List[T]): VenueList[T] = new VenueList(xs)
implicit def wrapCheckinList[T <: Checkin](xs: List[T]): CheckinList[T] = new CheckinList(xs)
/***** IMPLICITS FOR INNER PRIMING *** */
implicit def wrapCheckinPrimedVenueList[FKS, VenueT <: Venue]
(xs: List[Checkin with HasVenuePrimed[VenueT] with FKS]) = new CheckinPrimedVenueList(xs)
}
/* ******** END OF PRIMING LIBRARY ********* */
object Demos {
import Priming._
def printCheckinVenue(x: Checkin with HasVenuePrimed[Venue]): Unit = {
println("checkin %s at venue %s".format(x.checkinId, x.venue.get.venueId))
/* The next line will not compile since owner is not primed.. yay!
println("checkin %s at venue %s with owner %s".format(x.checkinId, x.venue.get.venueId, x.venue.get.owner.get.name))
*/
}
def printCheckinVenueOwner(x: Checkin with HasVenuePrimed[Venue with HasOwnerPrimed[Owner]]): Unit = {
println("checkin %s at venue %s with owner %s".format(x.checkinId, x.venue.get.venueId, x.venue.get.owner.get.name))
}
def printCheckinVenuePage(x: Checkin with HasVenuePrimed[Venue with HasPagePrimed[Page]]): Unit = {
println("checkin %s at venue %s with page %s".format(x.checkinId, x.venue.get.venueId, x.venue.get.page.get.name))
}
def printCheckinUserAndVenueOwner(x: Checkin with HasUserPrimed[User] with HasVenuePrimed[Venue with HasOwnerPrimed[Owner]]): Unit = {
println("%s created checkin %s by at venue %s with owner %s".format(x.user.get.name, x.checkinId, x.venue.get.venueId, x.venue.get.owner.get.name))
}
type FullyPrimedVenue = Venue with HasOwnerPrimed[Owner] with HasPagePrimed[Page]
type FullyPrimedCheckin = Checkin with HasUserPrimed[User] with HasVenuePrimed[FullyPrimedVenue]
def printFullCheckin(x: FullyPrimedCheckin): Unit = {
println("%s created checkin %s at venue %s with owner %s page %s".format(
x.user.get.name,
x.checkinId,
x.venue.get.venueId,
x.venue.get.owner.get.name,
x.venue.get.page.get.name
))
}
def main(argv: Array[String]) = {
val rawCheckins: List[Checkin] = List(MetaCheckin.createRecord(101, 201, 1), MetaCheckin.createRecord(102, 202, 2))
println("Priming demo...")
/* next line fails to compile as expected since venue is not primed! yay!
rawCheckins.foreach(printCheckinVenue)
*/
val primedVenues: List[Checkin with HasVenuePrimed[Venue]] = rawCheckins.primeVenue
println("\nprimedVenues")
primedVenues.foreach(printCheckinVenue)
/* next line fails to compile as expected since primeVenue gives us a base(unprimed) Venue! yay!
val primedVenuesFail: List[Checkin with HasVenuePrimed[Venue with HasOwnerPrimed[Owner]]] = rawCheckins.primeVenue
*/
// Deep priming!
val primedVenuesWithVenueOwners: List[Checkin with HasVenuePrimed[Venue with HasOwnerPrimed[Owner]]] = {
rawCheckins.primeVenue.primeInsideVenue(_.primeOwner)
}
println("\nprimedVenuesWithVenueOwners")
primedVenuesWithVenueOwners.foreach(printCheckinVenueOwner)
/* The following fails to compile as expected as page is not primed
primedVenuesWithVenueOwners.foreach(printCheckinVenuePage)
*/
val primeVenueOwnersAgain = primedVenuesWithVenueOwners.primeInsideVenue(_.primeOwner)
println("\nprimedVenuesWithVenueOwnersAGAIN")
primeVenueOwnersAgain.foreach(printCheckinVenueOwner)
val primedUserAndVenuesWithVenueOwners: List[Checkin with HasUserPrimed[User] with HasVenuePrimed[Venue with HasOwnerPrimed[Owner]]] = {
primedVenuesWithVenueOwners.primeUser
}
println("\nprimedUsersAndVenuesWithVenueOwners")
primedUserAndVenuesWithVenueOwners.foreach(printCheckinUserAndVenueOwner)
val allPrimed: List[Checkin with HasUserPrimed[User] with HasVenuePrimed[Venue with HasOwnerPrimed[Owner] with HasPagePrimed[Page]]] = {
primedUserAndVenuesWithVenueOwners.primeInsideVenue(_.primePage)
}
println("\nallPrimed")
allPrimed.foreach(printFullCheckin)
// The fully primed type can be used in functions that are not fully primed
allPrimed.foreach(printCheckinVenuePage)
allPrimed.foreach(printCheckinVenueOwner)
allPrimed.foreach(printCheckinVenue)
// TESTING TYPE INFERENCE BY NOT SPECIFYING TYPE INFORMATION
val allPrimedPermutation1 = {
rawCheckins.primeVenue.primeInsideVenue(_.primeOwner).primeInsideVenue(_.primePage).primeUser
}
val allPrimedPermutation2 = {
rawCheckins.primeVenue.primeInsideVenue(_.primePage).primeInsideVenue(_.primeOwner).primeUser
}
val allPrimedPermutation3 = {
rawCheckins.primeVenue.primeInsideVenue(_.primePage).primeInsideVenue(_.primeOwner).primeInsideVenue(_.primePage).primeUser
}
val allPrimedPermutation4 = {
rawCheckins.primeVenue.primeUser.primeInsideVenue(_.primeOwner).primeInsideVenue(_.primePage)
}
val allPrimedPermutation5 = {
rawCheckins.primeVenue.primeInsideVenue(_.primeOwner).primeUser.primeInsideVenue(_.primePage)
}
val allPrimedPermutation6 = {
rawCheckins.primeUser.primeVenue.primeInsideVenue(_.primeOwner).primeInsideVenue(_.primePage)
}
def checkInferredTypeIsEquivalentToFullyPrimedCheckin(x: List[FullyPrimedCheckin]) = ()
List(allPrimedPermutation1,
allPrimedPermutation2,
allPrimedPermutation3,
allPrimedPermutation4,
allPrimedPermutation5,
allPrimedPermutation6).foreach(checkInferredTypeIsEquivalentToFullyPrimedCheckin)
// TESTING DIRECT PRIMING, WITHOUT USING IMPLICITS
// Prime venues
val temp1 = primer.prime(rawCheckins, MetaCheckin, MetaVenue)(_.venueFk)
// Prime page and owner inside venues
val temp2 = primer.innerPrime(temp1, MetaCheckin, MetaVenue)(_.venueFk, primer.prime(_, MetaVenue, MetaPage)(_.pageFk))
val temp3 = primer.innerPrime(temp2, MetaCheckin, MetaVenue)(_.venueFk, primer.prime(_, MetaVenue, MetaOwner)(_.ownerFk))
// Prime user
val temp4 = primer.prime(temp3, MetaCheckin, MetaUser)(_.userFk)
checkInferredTypeIsEquivalentToFullyPrimedCheckin(temp4)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment