class: center, middle # Effects as Capabilities [Nicolas Rinaudo] • [@NicolasRinaudo@functional.cafe] --- class: center, middle name: direct # Direct Style --- ## Definition > A direct style of programming is one in which control is passed implicitly from one line of code to the next, and is opposed to continuation passing style (or, equivalently, monadic style), where it’s made explicit by a continuation. --- ## Definition > A direct style of programming is one in which you can write code without faffing about with monads. --- ## User input ```scala def readInput: Option[Int] = ``` --- ## User input ```scala def readInput: `Option[Int]` = ``` --- ## User input .diff-add[ ```scala def readInput: Option[Int] = * `readLine` ``` ] --- ## User input .diff-add[ ```scala def readInput: Option[Int] = * readLine`.toInt` ``` ] --- ## User input .diff-add[ ```scala def readInput: Option[Int] = * `try Some(`readLine.toInt`)` * `catch` * `case _ => None` ``` ] --- ## Single guess ```scala def guess(target: Int): Boolean = ``` --- ## Single guess ```scala def guess(`target: Int`): Boolean = ``` --- ## Single guess ```scala def guess(target: Int): `Boolean` = ``` --- ## Single guess .diff-add[ ```scala def guess(target: Int): Boolean = * `readInput` ``` ] --- ## Single guess .diff-add[ ```scala def guess(target: Int): Boolean = * readInput `match` ``` ] --- ## Single guess .diff-add[ ```scala def guess(target: Int): Boolean = readInput match * `case Some(‛target‛) =>` ``` ] --- ## Single guess .diff-add[ ```scala def guess(target: Int): Boolean = * readInput match * case Some(‛target‛) => `true` ``` ] --- ## Single guess .diff-add[ ```scala def guess(target: Int): Boolean = readInput match case Some(‛target‛) => true * `case Some(input) =>` ``` ] --- ## Single guess .diff-add[ ```scala def guess(target: Int): Boolean = readInput match case Some(‛target‛) => true case Some(input) => * `if input < target then println("Too low")` * `else println("Too high")` ``` ] --- ## Single guess .diff-add[ ```scala def guess(target: Int): Boolean = readInput match case Some(‛target‛) => true case Some(input) => if input < target then println("Too low") else println("Too high") * `false` ``` ] --- ## Single guess .diff-add[ ```scala def guess(target: Int): Boolean = readInput match case Some(‛target‛) => true case Some(input) => if input < target then println("Too low") else println("Too high") false * `case None =>` ``` ] --- ## Single guess .diff-add[ ```scala def guess(target: Int): Boolean = readInput match case Some(‛target‛) => true case Some(input) => if input < target then println("Too low") else println("Too high") false case None => * `println("Not a valid number")` ``` ] --- ## Single guess .diff-add[ ```scala def guess(target: Int): Boolean = readInput match case Some(‛target‛) => true case Some(input) => if input < target then println("Too low") else println("Too high") false case None => println("Not a valid number") * `false` ``` ] --- ## Looping on guesses ```scala def loop(target: Int, remaining: Int): Boolean = ``` --- ## Looping on guesses ```scala def loop(`target: Int`, remaining: Int): Boolean = ``` --- ## Looping on guesses ```scala def loop(target: Int, `remaining: Int`): Boolean = ``` --- ## Looping on guesses ```scala def loop(target: Int, remaining: Int): `Boolean` = ``` --- ## Looping on guesses .diff-add[ ```scala def loop(target: Int, remaining: Int): Boolean = * `if guess(target) then` ``` ] --- ## Looping on guesses .diff-add[ ```scala def loop(target: Int, remaining: Int): Boolean = if guess(target) then * `println("Congratulations!")` ``` ] --- ## Looping on guesses .diff-add[ ```scala def loop(target: Int, remaining: Int): Boolean = if guess(target) then println("Congratulations!") * `true` ``` ] --- ## Looping on guesses .diff-add[ ```scala def loop(target: Int, remaining: Int): Boolean = if guess(target) then println("Congratulations!") true * `else if remaining < 0 then` ``` ] --- ## Looping on guesses .diff-add[ ```scala def loop(target: Int, remaining: Int): Boolean = if guess(target) then println("Congratulations!") true else if remaining < 0 then * `println("🫵😂")` ``` ] --- ## Looping on guesses .diff-add[ ```scala def loop(target: Int, remaining: Int): Boolean = if guess(target) then println("Congratulations!") true else if remaining < 0 then println("🫵😂") * `false` ``` ] --- ## Looping on guesses .diff-add[ ```scala def loop(target: Int, remaining: Int): Boolean = if guess(target) then println("Congratulations!") true else if remaining < 0 then println("🫵😂") false * `else` ``` ] --- ## Looping on guesses .diff-add[ ```scala def loop(target: Int, remaining: Int): Boolean = if guess(target) then println("Congratulations!") true else if remaining < 0 then println("🫵😂") false else * `if remaining == 0 then println("Last attempt")` * `else println(s"$remaining tries left")` ``` ] --- ## Looping on guesses .diff-add[ ```scala def loop(target: Int, remaining: Int): Boolean = if guess(target) then println("Congratulations!") true else if remaining < 0 then println("🫵😂") false else if remaining == 0 then println("Last attempt") else println(s"$remaining tries left") * `loop(target, remaining - 1)` ``` ] --- ## Running the game ```scala def run: Boolean = ``` --- ## Running the game ```scala def run: `Boolean` = ``` --- ## Running the game .diff-add[ ```scala def run: Boolean = * `val target = nextInt(10)` ``` ] --- ## Running the game .diff-add[ ```scala def run: Boolean = val target = nextInt(10) * `println("Input a value between 0 and 10:")` ``` ] --- ## Running the game .diff-add[ ```scala def run: Boolean = val target = nextInt(10) println("Input a value between 0 and 10:") * `loop(target, 2)` ``` ] --- ## Running the game ```scala def run: Boolean = val target = `nextInt(10)` println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## Monads ```scala def run: Boolean = ??? ``` --- ## Monads .diff-rm[ ```scala *def run: `Boolean` = ??? ``` ] --- ## Monads .diff-add[ ```scala *def run: `RandT[PrintT[Read[Boolean]]]` = ??? ``` ] --- ## Monads .diff-rm[ ```scala *def run: `RandT[PrintT[Read[Boolean]]]` = ??? ``` ] --- ## Monads .diff-add[ ```scala *def run: `ZIO[Any, Nothing, Boolean]` = ??? ``` ] --- ## Monads .diff-rm[ ```scala *def run: `ZIO[Any, Nothing, Boolean]` = ??? ``` ] --- ## Monads .diff-add[ ```scala *def run[`F[_]: Monad]`: `F[Boolean]` = ??? ``` ] --- ## Monads .diff-add[ ```scala *def run[F[_]: Monad`: Rand: Print: Read`]: F[Boolean] = ??? ``` ] --- ## Monads ```scala val a: Int val b: Int a + b ``` --- ## Monads ```scala val oa: Option[Int] val ob: Option[Int] oa.flatMap: a => ob.map: fb => a + b ``` --- ## Monads ```scala def traverse[F[_]: Traverse, G[_]: Applicative, A, B] (fa: F[A])(f: A => G[B]): G[F[B]] = ??? def traverseM[F[_]: Monad, G[_]: Applicative, A, B] (fa: F[A])(f: A => G[F[B]]): G[F[B]] = ??? ``` --- ## Capabilities .diff-rm[ ```scala *def run[`F[_]: Rand: Print: Read]: F[Boolean]` = ??? ``` ] --- ## Capabilities .diff-add[ ```scala *def run: `Boolean` = ??? ``` ] --- ## Capabilities .diff-add[ ```scala *def run: `(Rand, Print, Read) =>` Boolean = ??? ``` ] --- ## Capabilities .diff-rm[ ```scala *def run: (Rand, Print, Read) `=>` Boolean = ??? ``` ] --- ## Capabilities .diff-add[ ```scala *def run: (Rand, Print, Read) `?=>` Boolean = ??? ``` ] --- ## Key takeaways -- * We want to track effects in types -- * Monads are a practical solution, with some drawbacks -- * Capabilities might be another, without these drawbacks --- ## Key takeaways * We want to track effects in types * Monads are a practical solution, with some drawbacks * Capabilities might be another, without these drawbacks (but maybe others) --- class: center, middle name: rand # Random Numbers --- ## The `Rand` Capability ```scala def run: Boolean = val target = `nextInt(10)` println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## The `Rand` Capability ```scala package rand trait Rand: ``` --- ## The `Rand` Capability .diff-add[ ```scala package rand trait Rand: * `def nextInt(max: Int): Int` ``` ] --- ## The `Rand` Capability ```scala package rand trait Rand: def nextInt(`max: Int`): Int ``` --- ## The `Rand` Capability ```scala package rand trait Rand: def nextInt(max: Int): `Int` ``` --- ## The `Rand` Capability ```scala def run: Boolean = val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## The `Rand` Capability .diff-add[ ```scala *def run: `Rand ?=>` Boolean = val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` ] --- ## The `Rand` Capability .diff-add[ ```scala *def run: Rand ?=> Boolean = `r ?=>` val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` ] --- ## The `Rand` Capability .diff-add[ ```scala def run: Rand ?=> Boolean = r ?=> * val target = `r.`nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` ] --- ## The `Rand` Capability ```scala def run: Rand ?=> Boolean = `r ?=>` val target = `r.`nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## The `Rand` Capability ```scala def run: Rand ?=> Boolean = r ?=> val target = `r.`nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## Atomic effectful computations ```scala def nextInt(max: Int): Rand ?=> Int = ``` --- ## Atomic effectful computations ```scala def nextInt(max: Int): `Rand ?=> Int` = ``` --- ## Atomic effectful computations .diff-add[ ```scala def nextInt(max: Int): Rand ?=> Int = * `r ?=> r.nextInt(max)` ``` ] --- ## Atomic effectful computations .diff-add[ ```scala def nextInt(max: Int): Rand ?=> Int = r ?=> r.nextInt(max) *`def range(min: Int, max: Int): Rand ?=> Int =` * `nextInt(max - min) + min` ``` ] --- ## Atomic effectful computations .diff-add[ ```scala def nextInt(max: Int): Rand ?=> Int = r ?=> r.nextInt(max) def range(min: Int, max: Int): Rand ?=> Int = nextInt(max - min) + min *`val lowerChar: Rand ?=> Char =` * `range('a'.toInt, 'z'.toInt).toChar` ``` ] --- ## Atomic effectful computations .diff-add[ ```scala def nextInt(max: Int): Rand ?=> Int = r ?=> r.nextInt(max) def range(min: Int, max: Int): Rand ?=> Int = nextInt(max - min) + min val lowerChar: Rand ?=> Char = range('a'.toInt, 'z'.toInt).toChar *`val upperChar: Rand ?=> Char =` * `range('A'.toInt, 'Z'.toInt).toChar` ``` ] --- ## Atomic effectful computations .diff-rm[ ```scala def run: Rand ?=> Boolean = r ?=> * val target = `r.`nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` ] --- ## Atomic effectful computations ```scala def run: Rand ?=> Boolean = r ?=> val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## Atomic effectful computations ```scala def run: Rand ?=> Boolean = r ?=> val target = `nextInt(10)` println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## Atomic effectful computations ```scala def run: Rand ?=> Boolean = r ?=> val `target` = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## Atomic effectful computations ```scala def run: Rand ?=> Boolean = r ?=> val target = nextInt(10) println("Input a value between 0 and 10:") loop(`target`, 2) ``` --- ## Eagerness of Context Functions ```scala def run: Rand ?=> Boolean = r ?=> val target = `nextInt(10)` println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## Eagerness of Context Functions .diff-add[ ```scala def run: Rand ?=> Boolean = r ?=> * val target = nextInt(10)`(using r)` println("Input a value between 0 and 10:") loop(target, 2) ``` ] --- ## ACCF ```scala def run: Rand ?=> Boolean = `r ?=>` val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## ACCF .diff-rm[ ```scala *def run: Rand ?=> Boolean = `r ?=>` val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` ] --- ## ACCF ```scala def run: Rand ?=> Boolean = val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## ACCF ```scala def run: Rand ?=> Boolean = val target = nextInt(10) println("Input a value between 0 and 10:") `loop(target, 2)` ``` --- ## ACCF ```scala def run: `Rand ?=> Boolean` = val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## ACCF .diff-add[ ```scala *def run: Rand ?=> Boolean = `(r: Rand) ?=>` val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` ] --- ## ACCF .diff-rm[ ```scala *def run: Rand ?=> Boolean = `(r: Rand) ?=>` val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` ] --- ## ACCF ```scala def run: Rand ?=> Boolean = val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## ACCF ```scala def run: `Rand ?=> Boolean` = val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## `Rand` handler ```scala def system[A](ra: Rand ?=> A): A = ``` --- ## `Rand` handler ```scala def system[A](`ra: Rand ?=> A`): A = ``` --- ## `Rand` handler ```scala def system[A](ra: Rand ?=> A): `A` = ``` --- ## `Rand` handler .diff-add[ ```scala def system[A](ra: Rand ?=> A): A = * `val handler = new Rand:` ``` ] --- ## `Rand` handler .diff-add[ ```scala def system[A](ra: Rand ?=> A): A = val handler = new Rand: * `val r = new scala.util.Random` ``` ] --- ## `Rand` handler .diff-add[ ```scala def system[A](ra: Rand ?=> A): A = val handler = new Rand: val r = new scala.util.Random * `override def nextInt(max: Int) =` ``` ] --- ## `Rand` handler .diff-add[ ```scala def system[A](ra: Rand ?=> A): A = val handler = new Rand: val r = new scala.util.Random override def nextInt(max: Int) = * `r.nextInt(max)` ``` ] --- ## `Rand` handler .diff-add[ ```scala def system[A](ra: Rand ?=> A): A = val handler = new Rand: val r = new scala.util.Random override def nextInt(max: Int) = r.nextInt(max) * `ra(using handler)` ``` ] --- ## `Rand` handler ```scala rand.system: run ``` --- ## `Test` handler ```scala def const[A](notRandom: Int)(ra: Rand ?=> A): A = ``` --- ## `Test` handler ```scala def const[A](`notRandom: Int`)(ra: Rand ?=> A): A = ``` --- ## `Test` handler ```scala def const[A](notRandom: Int)(`ra: Rand ?=> A`): A = ``` --- ## `Test` handler ```scala def const[A](notRandom: Int)(ra: Rand ?=> A): `A` = ``` --- ## `Test` handler .diff-add[ ```scala def const[A](notRandom: Int)(ra: Rand ?=> A): A = * `val handler = new Rand:` * `override def nextInt(max: Int) =` ``` ] --- ## `Test` handler .diff-add[ ```scala def const[A](notRandom: Int)(ra: Rand ?=> A): A = val handler = new Rand: override def nextInt(max: Int) = * `notRandom` ``` ] --- ## `Test` handler .diff-add[ ```scala def const[A](notRandom: Int)(ra: Rand ?=> A): A = val handler = new Rand: override def nextInt(max: Int) = notRandom * `ra(using handler)` ``` ] --- ## `Test` handler ```scala rand.const(5): assert(run) ``` --- ## Key takeaways -- * Capabilities are implicit parameters -- * We usually manipulate them through atomic computations -- * Concrete implementations (_handlers_) decide how programs behave --- class: center, middle name: print_read # User Interaction --- ## The `Print` Capability ```scala def run: Rand ?=> Boolean = val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## The `Print` Capability ```scala def run: Rand ?=> Boolean = val target = nextInt(10) `println("Input a value between 0 and 10:")` loop(target, 2) ``` --- ## The `Print` Capability ```scala package print trait Print: ``` --- ## The `Print` Capability .diff-add[ ```scala package print trait Print: * `def println(s: String): Unit` ``` ] --- ## The `Print` Capability ```scala package print trait Print: def println(`s: String`): Unit ``` --- ## The `Print` Capability ```scala package print trait Print: def println(s: String): `Unit` ``` --- ## The `Print` Capability .diff-add[ ```scala package print trait Print: def println(s: String): Unit *`def println(a: Any): Print ?=> Unit =` ``` ] --- ## The `Print` Capability ```scala package print trait Print: def println(s: String): Unit def println(`a: Any`): Print ?=> Unit = ``` --- ## The `Print` Capability ```scala package print trait Print: def println(s: String): Unit def println(a: Any): `Print ?=> Unit` = ``` --- ## The `Print` Capability .diff-add[ ```scala package print trait Print: def println(s: String): Unit def println(a: Any): Print ?=> Unit = * `p ?=> p.println(a.toString)` ``` ] --- ## The `Print` Capability ```scala def run: Rand ?=> Boolean = val target = nextInt(10) `println`("Input a value between 0 and 10:") loop(target, 2) ``` --- ## The `Print` Capability .diff-rm[ ```scala *def run: `Rand` ?=> Boolean = val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` ] --- ## The `Print` Capability .diff-add[ ```scala *def run: `(Rand, Print)` ?=> Boolean = val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` ] --- ## Order of capabilities ```scala def run: `(Rand, Print)` ?=> Boolean = val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## Order of capabilities ```scala def runSwapped: (Rand, Print) ?=> Boolean = run ``` --- ## Order of capabilities .diff-rm[ ```scala *def runSwapped: (`Rand, Print`) ?=> Boolean = run ``` ] --- ## Order of capabilities .diff-add[ ```scala *def runSwapped: `(Print, Rand)` ?=> Boolean = run ``` ] --- ## Order of capabilities ```scala def runSwapped: (Print, Rand) ?=> Boolean = `run` ``` --- ## Order of capabilities ```scala def runSwapped: `(Print, Rand) ?=> Boolean` = run ``` --- ## Order of capabilities .diff-add[ ```scala def runSwapped: (Print, Rand) ?=> Boolean = * `(p: Print, r: Rand) ?=>` run ``` ] --- ## Order of capabilities .diff-add[ ```scala def runSwapped: (Print, Rand) ?=> Boolean = * (p: Print, r: Rand) ?=> run`(using r, p)` ``` ] --- ## Propagating the capability ```scala def loop(target: Int, remaining: Int): Boolean = if guess(target) then println("Congratulations!") true else if remaining < 0 then println("🫵😂") false else if remaining == 0 then println("Last attempt") else println(s"$remaining tries left") loop(target, remaining - 1) ``` --- ## Propagating the capability ```scala def loop(target: Int, remaining: Int): Boolean = if guess(target) then `println`("Congratulations!") true else if remaining < 0 then `println`("🫵😂") false else if remaining == 0 then `println`("Last attempt") else `println`(s"$remaining tries left") loop(target, remaining - 1) ``` --- ## Propagating the capability .diff-rm[ ```scala *def loop(target: Int, remaining: Int): `Boolean` = if guess(target) then println("Congratulations!") true else if remaining < 0 then println("🫵😂") false else if remaining == 0 then println("Last attempt") else println(s"$remaining tries left") loop(target, remaining - 1) ``` ] --- ## Propagating the capability .diff-add[ ```scala *def loop(target: Int, remaining: Int): `Print ?=> Boolean` = if guess(target) then println("Congratulations!") true else if remaining < 0 then println("🫵😂") false else if remaining == 0 then println("Last attempt") else println(s"$remaining tries left") loop(target, remaining - 1) ``` ] --- ## Propagating the capability ```scala def guess(target: Int): Boolean = readInput match case Some(‛target‛) => true case Some(input) => if input < target then println("Too low") else println("Too high") false case None => println("Not a valid number") false ``` --- ## Propagating the capability ```scala def guess(target: Int): Boolean = readInput match case Some(‛target‛) => true case Some(input) => if input < target then `println`("Too low") else `println`("Too high") false case None => `println`("Not a valid number") false ``` --- ## Propagating the capability .diff-rm[ ```scala def guess(target: Int): `Boolean` = readInput match case Some(‛target‛) => true case Some(input) => if input < target then println("Too low") else println("Too high") false case None => println("Not a valid number") false ``` ] --- ## Propagating the capability .diff-add[ ```scala def guess(target: Int): `Print ?=> Boolean` = readInput match case Some(‛target‛) => true case Some(input) => if input < target then println("Too low") else println("Too high") false case None => println("Not a valid number") false ``` ] --- ## The `Read` Capability ```scala def readInput: Option[Int] = try Some(readLine.toInt) catch case _ => None ``` --- ## The `Read` Capability ```scala def readInput: Option[Int] = try Some(`readLine`.toInt) catch case _ => None ``` --- ## The `Read` Capability ```scala package read trait Read: ``` --- ## The `Read` Capability .diff-add[ ```scala package read trait Read: * `def readLine(): String` ``` ] --- ## The `Read` Capability ```scala package read trait Read: def readLine(): `String` ``` --- ## The `Read` Capability .diff-add[ ```scala package read trait Read: def readLine(): String *`val readLine: Read ?=> String =` ``` ] --- ## The `Read` Capability .diff-add[ ```scala package read trait Read: def readLine(): String val readLine: Read ?=> String = * `r ?=> r.readLine()` ``` ] --- ## The `Read` Capability ```scala def readInput: Option[Int] = try Some(`readLine`.toInt) catch case _ => None ``` --- ## The `Read` Capability .diff-rm[ ```scala *def readInput: `Option[Int]` = try Some(readLine.toInt) catch case _ => None ``` ] --- ## The `Read` Capability .diff-add[ ```scala *def readInput: `Read ?=> Option[Int]` = try Some(readLine.toInt) catch case _ => None ``` ] --- ## Propagating the capability ```scala def guess(target: Int): Print ?=> Boolean = readInput match case Some(‛target‛) => true case Some(input) => if input < target then println("Too low") else println("Too high") false case None => println("Not a valid number") false ``` --- ## Propagating the capability ```scala def guess(target: Int): Print ?=> Boolean = `readInput` match case Some(‛target‛) => true case Some(input) => if input < target then println("Too low") else println("Too high") false case None => println("Not a valid number") false ``` --- ## Propagating the capability .diff-rm[ ```scala *def guess(target: Int): `Print` ?=> Boolean = readInput match case Some(‛target‛) => true case Some(input) => if input < target then println("Too low") else println("Too high") false case None => println("Not a valid number") false ``` ] --- ## Propagating the capability .diff-add[ ```scala *def guess(target: Int): `(Read, Print)` ?=> Boolean = readInput match case Some(‛target‛) => true case Some(input) => if input < target then println("Too low") else println("Too high") false case None => println("Not a valid number") false ``` ] --- ## Propagating the capability ```scala def loop(target: Int, remaining: Int): Print ?=> Boolean = if guess(target) then println("Congratulations!") true else if remaining < 0 then println("🫵😂") false else if remaining == 0 then println("Last attempt") else println(s"$remaining tries left") loop(target, remaining - 1) ``` --- ## Propagating the capability ```scala def loop(target: Int, remaining: Int): Print ?=> Boolean = if `guess`(target) then println("Congratulations!") true else if remaining < 0 then println("🫵😂") false else if remaining == 0 then println("Last attempt") else println(s"$remaining tries left") loop(target, remaining - 1) ``` --- ## Propagating the capability .diff-rm[ ```scala *def loop(target: Int, remaining: Int): `Print` ?=> Boolean = if guess(target) then println("Congratulations!") true else if remaining < 0 then println("🫵😂") false else if remaining == 0 then println("Last attempt") else println(s"$remaining tries left") loop(target, remaining - 1) ``` ] --- ## Propagating the capability .diff-add[ ```scala def loop(target: Int, remaining: Int): `(Read, Print)` ?=> Boolean = if guess(target) then println("Congratulations!") true else if remaining < 0 then println("🫵😂") false else if remaining == 0 then println("Last attempt") else println(s"$remaining tries left") loop(target, remaining - 1) ``` ] --- ## Propagating the capability ```scala def run: (Rand, Print) ?=> Boolean = val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` --- ## Propagating the capability ```scala def run: (Rand, Print) ?=> Boolean = val target = nextInt(10) println("Input a value between 0 and 10:") `loop`(target, 2) ``` --- ## Propagating the capability .diff-rm[ ```scala *def run: `(Rand, Print)` ?=> Boolean = val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` ] --- ## Propagating the capability .diff-add[ ```scala *def run: `(Read, Rand, Print)` ?=> Boolean = val target = nextInt(10) println("Input a value between 0 and 10:") loop(target, 2) ``` ] --- ## A handler for user interaction ```scala package tty trait `Tty` extends Print with Read ``` --- ## A handler for user interaction ```scala package tty trait Tty extends `Print with Read` ``` --- ## A handler for user interaction ```scala def apply[A](ca: Tty ?=> A): A = ``` --- ## A handler for user interaction ```scala def apply[A](ca: `Tty ?=> A`): A = ``` --- ## A handler for user interaction ```scala def apply[A](ca: Tty ?=> A): `A` = ``` --- ## A handler for user interaction .diff-add[ ```scala def apply[A](ca: Tty ?=> A): A = * `val handler = new Tty:` ``` ] --- ## A handler for user interaction .diff-add[ ```scala def apply[A](ca: Tty ?=> A): A = val handler = new Tty: * `override def readLine() =` * `Console.in.readLine` ``` ] --- ## A handler for user interaction .diff-add[ ```scala def apply[A](ca: Tty ?=> A): A = val handler = new Tty: override def readLine() = Console.in.readLine * `override def println(s: String) =` * `Console.println(s)` ``` ] --- ## A handler for user interaction .diff-add[ ```scala def apply[A](ca: Tty ?=> A): A = val handler = new Tty: override def readLine() = Console.in.readLine override def println(s: String) = Console.println(s) * `ca(using handler)` ``` ] --- ## A handler for user interaction ```scala rand.system: run ``` --- ## A handler for user interaction .diff-add[ ```scala rand.system: * `tty:` * ` `run ``` ] --- ## Test `Print` handler ```scala def ignore[A](pa: Print ?=> A): A = ``` --- ## Test `Print` handler .diff-add[ ```scala def ignore[A](pa: Print ?=> A): A = * `val handler = new Print:` * `override def println(s: String) =` * `()` ``` ] --- ## Test `Print` handler .diff-add[ ```scala def ignore[A](pa: Print ?=> A): A = val handler = new Print: override def println(s: String) = () * `pa(using handler)` ``` ] --- ## Test `Read` handler ```scala def const[A](notRandom: String)(ra: Read ?=> A): A = ``` --- ## Test `Read` handler ```scala def const[A](`notRandom: String`)(ra: Read ?=> A): A = ``` --- ## Test `Read` handler .diff-add[ ```scala def const[A](notRandom: String)(ra: Read ?=> A): A = * `val handler = new Read:` * `override def readLine() =` * `notRandom` ``` ] --- ## Test `Read` handler .diff-add[ ```scala def const[A](notRandom: String)(ra: Read ?=> A): A = val handler = new Read: override def readLine() = notRandom * `ra(using handler)` ``` ] --- ## Testing our game ```scala print.ignore: ``` --- ## Testing our game .diff-add[ ```scala print.ignore: * `rand.const(5):` ``` ] --- ## Testing our game .diff-add[ ```scala print.ignore: rand.const(5): * `read.const("5"):` ``` ] --- ## Testing our game .diff-add[ ```scala print.ignore: rand.const(5): read.const("5"): * `assert(run)` ``` ] --- class: center, middle name: conclusion # In closing --- ## If you only remember 1 slide... Capabilities: -- * are dependency injection -- * allow a direct style of programming -- * encode effects in types --- class: center, middle name: questions [
][Slides] [Nicolas Rinaudo] • [@NicolasRinaudo@functional.cafe] --- ## TOC * [Direct Style](#direct) * [Random Numbers](#rand) * [User Interaction](#print_read) [@NicolasRinaudo@functional.cafe]:https://functional.cafe/@NicolasRinaudo [Nicolas Rinaudo]:https://nrinaudo.com/ [Slides]:https://nrinaudo.com/writing-pl/