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