Membrane
This is an example of a “permeable membrane” pattern in ocaps.
A permeable membrane wraps an object graph using a dependently typed monad to provide a policy effect. It differs from a “hard membrane” in that wrapping must be done manually and involves co-operation from participants, rather than being provided automatically.
You can read Confining Capabilities in the guide for more information.
import java.time.ZonedDateTime
import java.time.format.{DateTimeFormatter, FormatStyle}
import java.util.{Locale, TimeZone}
import cats.Id
import ocaps._
// http://blog.ezyang.com/2013/03/what-is-a-membran/
object Membrane {
import Location.{LocaleReader, TimeZoneReader}
class Location(locale: Locale, timeZone: TimeZone) {
private object capabilities {
val localeReader: Location.LocaleReader[Id] =
new Location.LocaleReader[Id] {
override val locale: Locale = Location.this.locale
}
val timeZoneReader: Location.TimeZoneReader[Id] =
new Location.TimeZoneReader[Id] {
override val timeZone: TimeZone = Location.this.timeZone
}
}
}
object Location {
trait LocaleReader[F[_]] {
def locale: F[Locale]
}
trait TimeZoneReader[F[_]] {
def timeZone: F[TimeZone]
}
class Access {
def localeReader(location: Location): LocaleReader[Id] = {
location.capabilities.localeReader
}
def timeZoneReader(location: Location): TimeZoneReader[Id] = {
location.capabilities.timeZoneReader
}
}
}
def main(args: Array[String]): Unit = {
class MembraneAccess(access: Location.Access, val membrane: PermeableMembrane) {
type Wrapper[+A] = membrane.Wrapper[A]
def localeReader(location: Location): LocaleReader[Wrapper] = {
new LocaleReader[Wrapper] {
override def locale: Wrapper[Locale] = {
membrane.wrap(access.localeReader(location).locale)
}
}
}
def timeZoneReader(location: Location): TimeZoneReader[Wrapper] = {
new TimeZoneReader[Wrapper] {
override def timeZone: Wrapper[TimeZone] = {
membrane.wrap(access.timeZoneReader(location).timeZone)
}
}
}
}
val m = RevokerMembrane()
val user = new Location(Locale.US, TimeZone.getTimeZone("PST"))
val access = new MembraneAccess(new Location.Access(), m)
val dryLocale: LocaleReader[access.Wrapper] = access.localeReader(user)
val dryTimeZone: TimeZoneReader[access.Wrapper] =
access.timeZoneReader(user)
// Use an IO monad because the wrapper could throw revokerException when we call get
// Uncommment this to see the operation fail...
//m.revoke()
val format: access.Wrapper[String] = for {
timeZone <- dryTimeZone.timeZone
locale <- dryLocale.locale
} yield {
ZonedDateTime
.now(timeZone.toZoneId)
.format(
DateTimeFormatter
.ofLocalizedDateTime(FormatStyle.FULL)
.withLocale(locale)
)
}
println(format.get)
}
}
Full source at GitHub
0.2.0