Construction with Subtypes

This example shows how to create capabilities from an abstract base type. You can read Constructing Capabilities in the guide for more information.

object Subtypes {

  import Fruit._

  sealed trait FruitState

  object FruitState {
    case object Uneaten extends FruitState
    case object Eaten extends FruitState
  }

  trait Fruit {
    import Fruit._

    private[this] var state: FruitState = FruitState.Uneaten

    def currentState: FruitState = state

    object capabilities {
      val eater: Eater[Fruit.this.type] = new Eater[Fruit.this.type] {
        def eat(): Fruit.this.type = {
          Fruit.this.state match {
            case FruitState.Uneaten =>
              Fruit.this.state = FruitState.Eaten
              Fruit.this

            case FruitState.Eaten =>
              throw new IllegalStateException("Already eaten!")
          }
        }
      }
    }
  }

  object Fruit {

    trait Eater[+F <: Fruit] {
      def eat(): F
    }

    object Eater {
      def logger[F <: Fruit](eater: Eater[F]): Eater[F] = new Eater[F] {
        override def eat(): F = {
          println("about to do the thing")
          eater.eat()
        }
      }
    }

    class Access {
      // Expose this capability raw, with no checks or anything.
      def eater[F <: Fruit](fruit: F): Eater[F] = fruit.capabilities.eater
    }
  }

  final class Apple extends Fruit {
    override def toString: String = s"Apple($currentState)"
  }

  final class Pear extends Fruit {
    override def toString: String = s"Pear($currentState)"
  }

  case class User(name: String, caps: Map[Fruit, Eater[_]] = Map.empty) {

    def canEat[F <: Fruit](fruit: F, eater: Eater[F]): User = {
      copy(caps = caps + (fruit -> eater))
    }

    def eats[F <: Fruit](fruit: F): Try[Option[F]] = {
      val triedFruit = Try {
        println(s"${name} eating $fruit:")

        caps.get(fruit).map { eater: Eater[_] =>
          val castEater = eater.asInstanceOf[Eater[F]] // Map strips off type info :-(
          Eater.logger(castEater).eat()
        }
      }
      println(s"  $triedFruit")
      triedFruit
    }
  }

  def main(args: Array[String]): Unit = {

    val access = new Access
    def grantEater[F <: Fruit](user: User, fruit: F): Eater[F] = {
      access.eater(fruit)
    }

    var steve = User("steve")
    val apple = new Apple()
    steve = steve.canEat(apple, grantEater(steve, apple))
    val eatenApple: Try[Option[Apple]] = steve.eats(apple)
    println(s"steve's apple = $eatenApple")

    val apple2 = new Apple()
    steve.eats(apple2)

    var mutt = User("mutt")

    val pear = new Pear()
    mutt = mutt.canEat(pear, grantEater(mutt, pear))
    val eatenPear: Try[Option[Pear]] = mutt.eats(pear)
    println(s"mutt's pear = $eatenPear")

    val jeff = User("jeff")
    val pear2 = new Pear()
    jeff.eats(pear2)
  }
}
Full source at GitHub