Construction with Effects

sourceobject Effects {
  type Id[A] = A

  final class Document(private var name: String) {
    import Document._
    private object capabilities {
      val nameChanger: NameChanger[Id] = new NameChanger[Id] {
        override def changeName(newName: String): Unit = {
          name = newName
        }
      }
    }
    override def toString: String = s"Document($name)"
  }

  object Document {
    trait NameChanger[F[_]] {
      def changeName(name: String): F[Unit]
    }

    trait WithEffect[C[_[_]], F[_]] {
      def apply(capability: C[Id]): C[F]
    }

    // The default "no effect" type Id[A] = A
    implicit val idEffect: NameChanger WithEffect Id = new WithEffect[NameChanger, Id] {
      override def apply(capability: NameChanger[Id]): NameChanger[Id] = identity(capability)
    }

    // Apply a "Try" effect to the capability
    implicit val tryEffect: NameChanger WithEffect Try = new WithEffect[NameChanger, Try] {
      override def apply(capability: NameChanger[Id]): NameChanger[Try] = new NameChanger[Try] {
        override def changeName(name: String): Try[Unit] = Try(capability.changeName(name))
      }
    }

    class Access {
      def nameChanger[F[_]](
        doc: Document
      )(implicit ev: NameChanger WithEffect F): NameChanger[F] = {
        val effect = implicitly[NameChanger WithEffect F]
        effect(doc.capabilities.nameChanger)
      }
    }
  }

  def main(args: Array[String]): Unit = {
    val document = new Document("will")
    val access = new Document.Access()

    val tryNameChanger = access.nameChanger[Try](document)
    tryNameChanger.changeName("steve") match {
      case Success(_) =>
        println(s"result = $document")
      case Failure(ex) =>
        println(s"exception = $ex")
    }

    // or...

    val idNameChanger = access.nameChanger[Id](document)
    idNameChanger.changeName("Will")
    println(s"result = $document")
  }
}
The source code for this page can be found here.