Created
March 18, 2013 05:50
-
-
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…
This file contains 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
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