Created
March 18, 2013 05:49
-
-
Save aposwolsky/5185291 to your computer and use it in GitHub Desktop.
type-safe priming -- specific example
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 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