Skip to content

Instantly share code, notes, and snippets.

@aposwolsky
Created March 18, 2013 05:49
Show Gist options
  • Save aposwolsky/5185291 to your computer and use it in GitHub Desktop.
Save aposwolsky/5185291 to your computer and use it in GitHub Desktop.
type-safe priming -- specific example
package demov4
/* **** 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
}
}
/* ******* 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 venueObj = new PrimedReference[PrivateRealVenue]
def venue: Option[PrivateRealVenue] = venueObj.cachedObj
}
abstract class PrivateRealVenue extends Venue with HasOwnerFK with HasPageFK
/* ******* CHECKIN MODEL ******* */
sealed abstract class Checkin {
def checkinId: Int
def venueId: Int
def userId: Int
}
abstract class PrivateRealCheckin extends Checkin with HasVenueFK with HasUserFK
/* ******* 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 userObj = new PrimedReference[PrivateRealUser]
def user: Option[PrivateRealUser] = userObj.cachedObj
}
/* ******* 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 ownerObj = new PrimedReference[PrivateRealOwner]
def owner: Option[PrivateRealOwner] = ownerObj.cachedObj
}
/* ******* 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 pageObj = new PrimedReference[PrivateRealPage]
def page: Option[PrivateRealPage] = pageObj.cachedObj
}
/* *** BUILDERS *** */
object Builder {
def createVenue(vId: Int, uId: Int, pId: Int): Venue = {
new PrivateRealVenue {
val venueId: Int = vId
val ownerId: Int = uId
val pageId: Int = pId
}
}
def createCheckin(cId: Int, vId: Int, uId: Int): Checkin = {
new PrivateRealCheckin {
val checkinId: Int = cId
val venueId: Int = vId
val userId: Int = uId
}
}
def createUser(id: Int): User = new PrivateRealUser {val userId: Int = id}
def createOwner(id: Int): Owner = new PrivateRealOwner {val ownerId: Int = id}
def createPage(id: Int): Page = new PrivateRealPage {val pageId: Int = id}
}
/* ******** PRIMING LIBRARY ********* */
object Priming {
class VenueList[VenueT <: Venue](xs: List[VenueT]) {
def primeOwner: List[VenueT with HasOwnerPrimed[Owner]] = {
val records = xs.asInstanceOf[List[PrivateRealVenue]]
records.foreach(x => {
val realOwnerObject = Builder.createOwner(x.ownerId)
x.ownerObj.primeObj(Some(realOwnerObject.asInstanceOf[PrivateRealOwner]))
})
records.asInstanceOf[List[VenueT with HasOwnerPrimed[Owner]]]
}
def primePage: List[VenueT with HasPagePrimed[Page]] = {
val records = xs.asInstanceOf[List[PrivateRealVenue]]
records.foreach(x => {
val realPageObject = Builder.createPage(x.pageId)
x.pageObj.primeObj(Some(realPageObject.asInstanceOf[PrivateRealPage]))
})
records.asInstanceOf[List[VenueT with HasPagePrimed[Page]]]
}
}
implicit def wrapVenueList[T <: Venue](xs: List[T]): VenueList[T] = new VenueList(xs)
class CheckinPrimedVenueList[FKS, VenueT <: Venue](xs: List[Checkin with HasVenuePrimed[VenueT] with FKS]) {
def primeInsideVenue[VenueTransformed <: VenueT](
transformer: (VenueList[VenueT] => VenueList[VenueTransformed])
) : List[Checkin with HasVenuePrimed[VenueTransformed] with FKS] = {
val records = xs.asInstanceOf[List[PrivateRealCheckin]]
transformer(records.flatMap(_.venue).asInstanceOf[List[VenueT]])
records.asInstanceOf[List[Checkin with HasVenuePrimed[VenueTransformed] with FKS]]
}
}
implicit def wrapCheckinPrimedVenueList[FKS, VenueT <: Venue](
xs: List[Checkin with HasVenuePrimed[VenueT] with FKS]
): CheckinPrimedVenueList[FKS, VenueT] = new CheckinPrimedVenueList(xs)
class CheckinList[T <: Checkin](xs: List[T]) {
def primeVenue: List[T with HasVenuePrimed[Venue]] = {
val records = xs.asInstanceOf[List[PrivateRealCheckin]]
records.foreach(x => {
val realVenueObject = Builder.createVenue(x.venueId, x.venueId + 100, x.venueId + 200)
x.venueObj.primeObj(Some(realVenueObject.asInstanceOf[PrivateRealVenue]))
})
records.asInstanceOf[List[T with HasVenuePrimed[Venue]]]
}
def primeUser: List[T with HasUserPrimed[User]] = {
val records = xs.asInstanceOf[List[PrivateRealCheckin]]
records.foreach(x => {
val realUserObject = Builder.createUser(x.userId)
x.userObj.primeObj(Some(realUserObject.asInstanceOf[PrivateRealUser]))
})
(records.asInstanceOf[List[T with HasUserPrimed[User]]])
}
}
implicit def wrapCheckinList[T <: Checkin](xs: List[T]): CheckinList[T] = new CheckinList(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(Builder.createCheckin(101, 201, 1), Builder.createCheckin(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)
// The order that we prime fields does not mater, and we do not need to specify types to detect it is fully primed
// 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)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment