Skip to content

Instantly share code, notes, and snippets.

@changlinli
Created March 19, 2018 04:45
Show Gist options
  • Save changlinli/b4dee7273cae1de4f6b262afe0eb6ca7 to your computer and use it in GitHub Desktop.
Save changlinli/b4dee7273cae1de4f6b262afe0eb6ca7 to your computer and use it in GitHub Desktop.
Using path dependent types to ensure invariants in Scala
object StandardUnit {
type PersonId = Int
final class Person(id: PersonId, name: String)
object Person {
def lookupName(id: PersonId): Option[String] = ???
def lookupBlacklistStatus(id: PersonId): Option[Unit] = ???
def generatePerson(id: PersonId)(associatedName: String): Person = {
new Person(id, associatedName)
}
}
val exampleId: PersonId = 5
val myPerson: Option[Person] = for {
_ <- Person.lookupBlacklistStatus(exampleId)
person <- Person.generatePerson(exampleId)
} yield person
}
object UsingPathDependentTypes {
type PersonId = Int
final case class IdRelations(underlyingId: PersonId) {
final class NameAssociatedWithId private (val name: String)
final class IsNotBlackListed()
}
// You might actually want to store IdRelations, IsNotBlackListed, on Person
// as well to allow downstream consumers to make use of these facts
final class Person private (id: PersonId, name: String)
object Person {
def lookupName(id: IdRelations): Option[id.NameAssociatedWithId] = ???
def lookupBlacklistStatus(id: IdRelations): Option[id.IsNotBlackListed] = ???
def generatePerson(id: IdRelations)(associatedName: id.NameAssociatedWithId, proofNotBlacklisted: id.IsNotBlackListed): Person = {
new Person(id.underlyingId, associatedName.name)
}
}
val exampleId: PersonId = 5
val relationsForExampleId: IdRelations = IdRelations(exampleId)
val anotherId: PersonId = 6
val relationsForAnotherId: IdRelations = IdRelations(anotherId)
val myPerson: Option[Person] = for {
notBlacklisted <- Person.lookupBlacklistStatus(relationsForExampleId)
name <- Person.lookupName(relationsForExampleId)
} yield Person.generatePerson(relationsForExampleId)(name, notBlacklisted)
// Does not compile
val myIncorrectPerson: Option[Person] = for {
// I tried to look up the black list status of an incorrect Person ID
proofNotBlacklisted <- Person.lookupBlacklistStatus(relationsForAnotherId)
name <- Person.lookupName(relationsForExampleId)
} yield Person.generatePerson(relationsForExampleId)(name, proofNotBlacklisted)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment