Created
March 19, 2018 04:45
-
-
Save changlinli/b4dee7273cae1de4f6b262afe0eb6ca7 to your computer and use it in GitHub Desktop.
Using path dependent types to ensure invariants in Scala
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
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