Effect
Capture
Effect
Oleg Nizhnikov ^ {Evolution, Gaming}




Benefits of IO
Benefits of IO

Benefits of IO

Direct Style

Direct Style


Capture Checking

Capture Checking


AGENDA
Effects

Uniqueness

Lifetimes
Capture Checking


Sequents
STLC
Context
class Orders(customerRepo: CustomerRepo) { def products(order: Order): IO[List[Products]] = { val productIds: List[ProductIds] = order.items.flatMap(_.productIds) productIds.traverse{id : CustomerId => customerRepo.getById(id) } } }
Context
class Orders(customerRepo: CustomerRepo) { def products(order: Order): IO[List[Products]] = { val productIds: List[ProductIds] = order.items.flatMap(_.productIds) productIds.traverse{id : CustomerId => customerRepo.getById(id) } } }
Г

Context
class Orders(customerRepo: CustomerRepo) { def products(order: Order): IO[List[Products]] = { val productIds: List[ProductIds] = order.items.flatMap(_.productIds) productIds.traverse{id : CustomerId => customerRepo.getById(id) } } }
Г
customerRepo: CustomerRepo,
order: Order,
productIds: List[ProductIds],
id : CustomerId,...
Variable
class Orders(customerRepo: CustomerRepo) { def products(order: Order): IO[List[Products]] = { val productIds: List[ProductIds] = order.items.flatMap(_.productIds) productIds.traverse{id : CustomerId => customerRepo.getById(id) } } }
customerRepo
id
Application
class Orders(customerRepo: CustomerRepo) { def products(order: Order): IO[List[Products]] = { val productIds: List[ProductIds] = order.items.flatMap(_.productIds) productIds.traverse{id : CustomerId => customerRepo.getById(id) } } }
f = customerRepo.getById
v = id
f = order.items.flatMap
v = _.productIds
Lambda
class Orders(customerRepo: CustomerRepo) { def products(order: Order): IO[List[Products]] = { val productIds: List[ProductIds] = order.items.flatMap(_.productIds) productIds.traverse{id : CustomerId => customerRepo.getById(id) } } }
{ id: CustomerId => ... }
_.productIds
let
class Orders(customerRepo: CustomerRepo) { def products(order: Order): IO[List[Products]] = { val productIds: List[ProductIds] = order.items.flatMap(_.productIds) productIds.traverse{id : CustomerId => customerRepo.getById(id) } } }
x = productIds
Free
Free
id
Free(e) = {id}
Free
customerRepo.getById(id)
Free(e) = {customerRepo, id}
Free
{id : CustomerId => customerRepo.getById(id) }
Free(e) = {customerRepo}
Free
val productIds: List[ProductIds] = order.items.flatMap(_.productIds) productIds.traverse{id : CustomerId => customerRepo.getById(id) }
Free(e) = {order, customerRepo}
Free
def products(order: Order): IO[List[Products]] = { val productIds: List[ProductIds] = order.items.flatMap(_.productIds) productIds.traverse{id : CustomerId => customerRepo.getById(id) } }
Free(e) = {customerRepo}
Free
class Orders(customerRepo: CustomerRepo) { def products(order: Order): IO[List[Products]] = { val productIds: List[ProductIds] = order.items.flatMap(_.productIds) productIds.traverse{id : CustomerId => customerRepo.getById(id) } } }
Free(e) = {}
Free
class Orders(customerRepo: CustomerRepo) { def products(order: Order): IO[List[Products]] = { val productIds: List[ProductIds] = order.items.flatMap(_.productIds) productIds.traverse{id : CustomerId => customerRepo.getById(id) } } }
Free(e) = {}
Top-level terms are closed
Effects
Effects
- Calculation
- Unlimited recursion
- Allocating on stack
- Allocating on heap
- Reading global variables
- Aborting execution with an error
- Operating on locally mutable state
- Operating on shared mutable state
- Waiting
- Concurrent execution
- Communicating between subprocesses
- Reading from file system
- Writing to file system
- Network communication
Effects
- Calculation
- Unlimited recursion
- Allocating on stack
- Allocating on heap
- Reading global variables
- Aborting execution with an error
- Operating on locally mutable state
- Operating on shared mutable state
- Waiting
- Concurrent execution
- Communicating between subprocesses
- Reading from file system
- Writing to file system
- Network communication

Effects
- Calculation
- Unlimited recursion
- Allocating on stack
- Allocating on heap
- Reading global variables
- Aborting execution with an error
- Operating on locally mutable state
- Operating on shared mutable state
- Waiting
- Concurrent execution
- Communicating between subprocesses
- Reading from file system
- Writing to file system
- Network communication
Non-Effects
Effects
Effects
- Calculation
- Unlimited recursion
- Allocating on stack
- Allocating on heap
- Reading global variables
- Aborting execution with an error
- Operating on locally mutable state
- Operating on shared mutable state
- Waiting
- Concurrent execution
- Communicating between subprocesses
- Reading from file system
- Writing to file system
- Network communication
Non-Effects
Effects
Unison
Paul Chiusano
Runar Bjarnason
Chris Penner

Unison
readInt : Text -> Int readInt text = Optional.getOrElse +0 (Int.fromText text) readInts: Text -> List Int readInts text = List.map readInt (Text.split ?, text)
Unison
readInt: Text -> Int readInt = fromText >> getOrElse +0 readInts: Text -> [Int] readInts = split ?, >> map readInt
Unison
readInt': Text -> {Abort} Int readInt' = Int.fromText >> toAbort readInts': Text -> {Abort} [Int] readInts' = split ?, >> map readInt' readIntsOrEmpty : Text -> {} [Int] readIntsOrEmpty txt = toDefaultValue! [] '(readInts' txt)
readInt': Text -> {Abort} Int readInt' = Int.fromText >> toAbort readInts': Text -> {Abort} [Int] readInts' = split ?, >> map readInt' readIntsOrEmpty : Text -> {} [Int] readIntsOrEmpty txt = toDefaultValue! [] '(readInts' txt)
readInt': Text -> {Abort} Int readInt' = Int.fromText >> toAbort readInts': Text -> {Abort} [Int] readInts' = split ?, >> map readInt' readIntsOrEmpty : Text -> {} [Int] readIntsOrEmpty txt = toDefaultValue! [] '(readInts' txt)
Unison Effects
structural ability Abort where stop: forall a. {Abort} a toAbort : Optional a -> {Abort} a toAbort opt = match opt with None -> stop Some a -> a toDefaultValue! : a -> '{Abort} a -> a toDefaultValue! value expr = handle force expr with cases { Abort.stop -> k } -> value { result } -> result
structural ability Abort where stop: forall a. {Abort} a toAbort : Optional a -> {Abort} a toAbort opt = match opt with None -> stop Some a -> a toDefaultValue! : a -> '{Abort} a -> a toDefaultValue! value expr = handle force expr with cases { Abort.stop -> k } -> value { result } -> result
structural ability Abort where stop: forall a. {Abort} a toAbort : Optional a -> {Abort} a toAbort opt = match opt with None -> stop Some a -> a toDefaultValue! : a -> '{Abort} a -> a toDefaultValue! value expr = handle force expr with cases { Abort.stop -> k } -> value { result } -> result
Unison Effects
parseInt: Text -> {Abort} Int parseInt = toAbort << Int.fromText > (getLine : Handle -> {IO, Exception} Text) parseNextLine : Handle ->{IO, Exception, Abort} Int parseNextLine = getLine >> parseInt > (open: FilePath -> FileMode -> {IO, Exception} Handle) parseFileInts : Text ->{IO, Exception, Abort} [Int] parseFileInts path = file = open (FilePath path) Read l1 = parseNextLine file l2 = parseNextLine file l3 = parseNextLine file [l1, l2, l3]
parseInt: Text -> {Abort} Int parseInt = toAbort << Int.fromText > (getLine : Handle -> {IO, Exception} Text) parseNextLine : Handle ->{IO, Exception, Abort} Int parseNextLine = getLine >> parseInt > (open: FilePath -> FileMode -> {IO, Exception} Handle) parseFileInts : Text ->{IO, Exception, Abort} [Int] parseFileInts path = file = open (FilePath path) Read l1 = parseNextLine file l2 = parseNextLine file l3 = parseNextLine file [l1, l2, l3]
parseInt: Text -> {Abort} Int parseInt = toAbort << Int.fromText > (getLine : Handle -> {IO, Exception} Text) parseNextLine : Handle ->{IO, Exception, Abort} Int parseNextLine = getLine >> parseInt > (open: FilePath -> FileMode -> {IO, Exception} Handle) parseFileInts : Text ->{IO, Exception, Abort} [Int] parseFileInts path = file = open (FilePath path) Read l1 = parseNextLine file l2 = parseNextLine file l3 = parseNextLine file [l1, l2, l3]
Unison Effects
sqrt : Float -> {Abort} Float sqrt n = if n < 0.0 then abort else Float.sqrt n sqrtSum: Float -> Float -> {Abort} Float sqrtSum x y = sqrt x + sqrt y
Unison Effects
sqrt : Float -> {Abort} Float sqrt n = if n < 0.0 then abort else Float.sqrt n sqrtSum': Float -> Float -> Float sqrtSum' x = sqrtX = sqrt x y -> sqrtX + sqrt y
Unison Effects
sqrt : Float -> {Abort} Float sqrt n = if n < 0.0 then abort else Float.sqrt n sqrtSum': Float -> Float -> Float sqrtSum' x = sqrtX = sqrt x y -> sqrtX + sqrt y
Float -> {} Float -> {Abort} Float
Float -> {Abort} Float -> {} Float
Float ->{Abort} Float ->{Abort} Float
✔
❌
❌
Unison Effects
> (open: FilePath -> FileMode -> {IO, Exception} Handle) > (parseInt: Text -> {Abort} Int) > (getLine : Handle -> {IO, Exception} Text) parseFileLines path = file = open (FilePath path) Read i -> i + parseInt (getLine file)
Text -> {} Int -> {IO, Exception, Abort} Int
Text -> {IO, Exception, Abort} Int -> {} Int
Text ->{IO, Exception} Int ->{IO, Exception, Abort} Int
✔
❌
❌
Effectful Lambda
Replacing Monads with effects
Optional a Either e a Writer w a\List w Reader r a State s a Try a IO a
{Abort} {Throw e} {Stream w} {Context r} {Store s} {Exception} {IO}
Effect handlers
unique ability GenerateInt where next: {GenerateInt} Int const : Int -> '{GenerateInt, e} a ->{e} a const value expr = handle !expr with cases { next -> k} -> const value '(k value) { res } -> res
k : Int -> '{GenerateInt, e} a
> const +2 '[next, next, next]
⧩
[+2, +2, +2]
Effect handlers
unique ability GenerateInt where next: {GenerateInt} Int from : Int -> '{GenerateInt, e} a -> {e} a from start expr = handle !expr with cases { next -> k} -> from (start + +1) '(k start) { res } -> res
> from +2 '[next, next, next]
⧩
[+2, +3, +4]
Effect handlers
unique ability GenerateInt where next: {GenerateInt} Int fallBack : a -> '{GenerateInt, e} a -> {e} a fallBack default expr = handle !expr with cases { next -> _ } -> default { res } -> res
> fallBack [+1] '[next, next] ⧩ [+1] > fallBack [+1] '[] ⧩ []
Effect handlers
unique ability GenerateInt where next: {GenerateInt} Int withRandom : '{GenerateInt, e} a -> {Random, e} a withRandom expr = handle !expr with cases { next -> k} -> withRandom '(k !Random.int) { res } -> res
> splitmix 1000 '(withRandom '[next, next, next]) ⧩ [-8103687766989403219, +7503088898446438863, -8052551751343754592]
Effect handlers
unique ability GenerateInt where next: {GenerateInt} Int withPrint : '{GenerateInt, e} a -> {IO, Exception, e} a withPrint expr = handler = cases { next -> k} -> x = !Random.int printLine ("generated " ++ toText x) res = handle k x with handler printLine "done" res { res } -> res Random.run do handle !expr with handler
Effect handlers
unique ability GenerateInt where next: {GenerateInt} Int search : List Int -> '{GenerateInt, e} Optional a -> {e} Optional a search values expr = handle !expr with cases { next -> k} -> List.findMap (i -> search values '(k i)) values { res } -> res
pythagorean : '{GenerateInt} Optional (Int, Int, Int) pythagorean = do a = next b = next c = next if (a * a) + (b * b) == (c * c) then Some (a, b, c) else None > search (Int.range +1 +8) pythagorean ⧩ Some (+3, +4, +5)
Effect handlers
- Abstraction and Inversion of Control
- Error Handling
- Resource and Scope Control
- Branching and Backtracking
- (Async execution)
unique ability GenerateInt where next: {GenerateInt} Int
Effect
- Composition is hard
- eDSL
- No partial handling
- Same but with aftertaste
vs
Monads
- Composable
- Special language feature
- Partial handlers
- Covers a lot of language features



State
case class State[S, A](run: S => (S, A)) case class State[S, A](run: Eval[S => Eval[(S, A)]]) trait State[S, +A]{ def run(s: S): (S, A) }
State Monad
object State{ def apply[S, A](a: A): State[S, A] = s => (s, a) } trait State[S, +A]{ def run(s: S): (S, A) def map[B](f: A => B): State[S, B] = { s => val (s1, a) = run(s) (s1, f(a)) } def flatMap[B](f: A => State[S, B]) = { s => val (s1, a) = run(s) f(a).run(s1) } }
State Monad
object State{ def apply[S, A](a: A): State[S, A] = s => (s, a) def get[S]: State[S, S] = s => (s, s) def set[S](s: S): State[S, Unit] = _ => (s, ()) def update[S](f: S => S): State[S, Unit] = s => (f(s), ()) }
State Comprehension
def addClient(client: Client): State[WowEnterpriseData, Unit] = for { data <- State.get clients = data.clients.updated(client.id, client) _ <- State.set(data.copy(clients = clients)) } yield () def addProduct(product: Product): State[WowEnterpriseData, Unit] = for { data <- State.get products = data.products.updated(product.id, product) _ <- State.set(data.copy(products = products)) } yield () def addUsage(clientId: ClientId, productId: ProductId): State[WowEnterpriseData, Unit] = for { data <- State.get usage = data.usage + ((clientId, productId)) _ <- State.set(data.copy(usage = usage)) } yield () def suchEnterpiseLogic(client: Client, product: Product): State[WowEnterpriseData, Unit] = for { _ <- addClient(client) _ <- addProduct(product) _ <- addUsage(client.id, product.id) } yield () case class WowEnterpriseData( clients: Map[ClientId, Cliend], products: Map[ProductId, Product], usage: Set[(ClientId, ProductId)] )
WARNING: THIS IS NOT REAL ENTERPRISE-LEVEL CODE
State Comprehension
clients = data.clients.updated(client.id, client) data.copy(clients = clients) products = data.products.updated(product.id, product) data.copy(products = products) usage = data.usage + ((clientId, productId)) data.copy(usage = usage) case class WowEnterpriseData( clients: Map[ClientId, Cliend], products: Map[ProductId, Product], usage: Set[(ClientId, ProductId)] )
clients = data.clients.updated(client.id, client) data.copy(clients = clients) products = data.products.updated(product.id, product) data.copy(products = products) usage = data.usage + ((clientId, productId)) data.copy(usage = usage) case class WowEnterpriseData( clients: Map[ClientId, Cliend], products: Map[ProductId, Product], usage: Set[(ClientId, ProductId)] )
Mutable State
clients = data.clients.updated(client.id, client) data.clients = clients products = data.products.updated(product.id, product) data.products = products usage = data.usage + ((clientId, productId)) data.usage = usage class WowEnterpriseData( var clients: Map[ClientId, Cliend], var products: Map[ProductId, Product], var usage: Set[(ClientId, ProductId)] )
Mutable Danger
def reportState(using MyState): String -> String = { name => s"$name current value is ${MyState.count}\n" } def compare(using MyState): String = { val oldReport = reportState for (i <- 0 to 9) { MyState.increment() } val newReport = reportState oldReport("old") ++ newReport("new") } @main def runMyState(): Unit = MyState.withState{ println(compare) }
Mutable Danger
def reportState(using MyState): String -> String = { name => s"$name current value is ${MyState.count}\n" } def compare(using MyState): String = { val oldReport = reportState for (i <- 0 to 9) { MyState.increment() } val newReport = reportState oldReport("old") ++ newReport("new") } @main def runMyState(): Unit = MyState.withState{ println(compare) } // old current value is 10 // new current value is 10
def reportState(using MyState): String -> String = { name => s"$name current value is ${MyState.count}\n" } def compare(using MyState): String = { val oldReport = reportState for (i <- 0 to 9) { MyState.increment() } val newReport = reportState oldReport("old") ++ newReport("new") } @main def runMyState(): Unit = MyState.withState{ println(compare) } // old current value is 10 // new current value is 10
def reportState(using MyState): String -> String = { name => s"$name current value is ${MyState.count}\n" } def compare(using MyState): String = { val oldReport = reportState for (i <- 0 to 9) { MyState.increment() } val newReport = reportState oldReport("old") ++ newReport("new") } @main def runMyState(): Unit = MyState.withState{ println(compare) } // old current value is 10 // new current value is 10
Mutable Danger = Capture
def reportState(using MyState): String -> String = { val count = MyState.count name => s"$name current value is $count\n" } def compare(using MyState): String = { val oldReport = reportState for (i <- 0 to 9) { MyState.increment() } val newReport = reportState oldReport("old") ++ { newReport("new") } } @main def runMyState(): Unit = MyState.withState{ println(compare) } // old current value is 0 // new current value is 10
Unique Mutable State
def doSomething(data: Data): (Result, Data) = val data1 = data.copy(field1 = value1) val data2 = data1.copy(field2 = value2) (result, data2)
Unique Mutable State
def doSomething(data: Data): (Result, Data) = val data1 = data.copy(field1 = value1) val data2 = data1.copy(field2 = value2) (result, data2)
there is only one reference to data
def doSomething(data: Data): (Result, Data) = data.field1 = value1 data.field2 = value2 (result, data)
- Automatically rewrite updates to mutation if there is only one reference, a.k.a Functional But In Place (Perseus: Koka + Lean4)
- Track uniqueness in type system, allow direct mutability
Unique Mutable State
- Automatically rewrite updates to mutation if there is only one reference, a.k.a Functional But In Place (Perseus: Koka + Lean4)
- Track uniqueness in type system, allow direct mutability (Clean)
(S) -> (S, A)
(*unique S) -> A
Unique Mutable State
Uniqueness Types
def reportState(using unique MyState): String -> String = { name => s"$name current value is ${MyState.count}\n" }

Uniqueness Types
def reportState(using unique MyState): String -> String = { name => s"$name current value is ${MyState.count}\n" }
def reportState(using unique MyState): String -> String = { val count = MyState.count name => s"$name current value is $count\n" }
Unique Lambda
Unique Lambda
Effectful Lambda
Uniqueness as Effect
class IO object Console { def readLine(unique io: IO): String = ??? def writeLine(line: String)(unique io: IO): Unit = ??? } class File object File { def open[A](use: (unique File, unique IO) -> A)(unique io: IO): A = ??? def getLine(unique file: File): String = ??? def putLine(string: String)(unique file: File): String = ??? }
Uniqueness
- Usual types
- no automatic continuations
- refer to values by names
- several vars of the same unique type
- requires annotations
- compose unique values in structures
vs
Effect
- Separate kind, special syntax
- continuations in handlers
- only refer to effect by its methods
- refer to effect by type
- effect inference
- define complex handlers
Rust
Borrowed
Mut
Owned
Rust
Shared
Unique
Owned
static
Affine
struct Account { name: String, funds: u64, } fn add_funds(a: Account, add: u64) -> Account { Account { funds: a.funds + add, ..a } }
Affine
struct Account { name: String, funds: u64, } fn add_funds(mut a: Account, add: u64) -> Account { a.funds += add; a }
Affine
struct Account { name: String, funds: u64, } fn add_funds(mut a: Account, add: u64) -> Account { a.funds += add; a } fn foo() { let mut a = Account { name: "Oleg".to_string(), funds: 1000, }; let a1 = add_funds(a, 5); let a2 = add_funds(a, 5); }
use of moved value: `a`
value used here after move
Affine
struct Account { name: String, funds: u64, } fn add_funds(mut a: Account, add: u64) -> Account { a.funds += add; a } fn foo() { let mut a = Account { name: "Oleg".to_string(), funds: 1000, }; let a1 = add_funds(a, 5); let a2 = add_funds(a1, 5); }
Affine
Moving
struct Account { name: String, funds: u64, } fn add_funds(mut a: Account, add: u64) -> Account { a.funds += add; a } fn foo() { let mut a = Account { name: "Oleg".to_string(), funds: 25, }; let a1 = add_funds(a, 5); let a2 = add_funds(a1, 5); }
Referencing
struct Account { name: String, funds: u64, } fn add_funds(a: &mut Account, add: u64) { a.funds += add; } fn foo() { let mut a = Account { name: "Oleg".to_string(), funds: 1000 , }; add_funds(&mut a, 5); add_funds(&mut a, 5); }
Referencing
struct Account { name: String, funds: u64, } fn add_funds(a: &mut Account, add: u64) { a.funds += add; } fn foo() { let mut a = Account { name: "Oleg".to_string(), funds: 1000 , }; let ar1 = &mut a; add_funds(ar1, 5); let ar2 = &mut a; add_funds(ar2, 5); }
Referencing?
struct Account { name: String, funds: u64, } fn add_funds(a: &mut Account, add: u64) { a.funds += add; } fn foo() { let mut a = Account { name: "Oleg".to_string(), funds: 1000 , }; let ar1 = &mut a; let ar2 = &mut a; add_funds(ar1, 5); add_funds(ar2, 5); }
cannot borrow `a` as mutable more than once at a time
Lifetimes
There is only one mutable reference to each portion of data
Uniqueness Types
Lifetimes
struct Account { name: String, funds: u64, } fn add_funds(a: &mut Account, add: u64) { a.funds += add; } fn foo() { let mut a = Account { name: "Oleg".to_string(), funds: 1000 , }; let ar1 = &mut a; add_funds(ar1, 5); let ar2 = &mut a; add_funds(ar2, 5); }
Lifetimes
struct Account { name: String, funds: u64, } fn add_funds<'a>(a: &'a mut Account, add: u64) { a.funds += add; } fn foo() { let mut a = Account { name: "Oleg".to_string(), funds: 1000 , }; let ar1 = &mut a; add_funds(ar1, 5); let ar2 = &mut a; add_funds(ar2, 5); }
Lifetimes
struct Funds<'a> { account: &'a mut Account, } impl <'a> Funds<'a> { fn add(self, add: u64) { self.account.funds += add; } } fn funds<'a>(account: &'a mut Account) -> Funds<'a> { Funds { account } } fn foo() { let mut a = Account { name: "Oleg".to_string(), funds: 1000, }; funds(&mut a).add(5); funds(&mut a).add(5); }
Lifetimes
struct Transfer<'a> { from: &'a mut Account, to: &'a mut Account, } impl <'a> Transfer<'a> { fn transfer(self, amount: u64) { self.from.funds -= amount; self.to.funds += amount; } } fn transfer<'a>(from: &'a mut Account, to: &'a mut Account) -> Transfer<'a> { Transfer { from, to } } fn foo() { let mut a = Account { name: "Oleg".to_string(), funds: 1000 }; let mut b = Account { name: "Vasya".to_string(), funds: 1000 }; transfer(&mut a, &mut b).transfer(30); funds(&mut a).add(5); funds(&mut a).add(5); }
Precise Capture
struct Transfer<'a> { to: &'a mut Account, amount: u64, } impl<'a> Transfer<'a> { fn commit(self) { self.to.funds += self.amount; } } fn transfer<'a, 'b>(from: &'a mut Account, to: &'b mut Account, amount: u64) -> Transfer<'b> { from.funds -= amount; Transfer { to, amount } } fn foo() { let mut a = Account { name: "Oleg".to_string(), funds: 1000 }; let mut b = Account { name: "Vasya".to_string(), funds: 1000 }; let t = transfer(&mut a, &mut b, 10); funds(&mut a).add(5); t.commit(); funds(&mut b).add(5); }
Self reference problem
struct OwnedAndRef { data: Vec<i32>, element: &mut '(data) i32 }

Rust Mutable Refs
- perform uniqueness check
- can express precise capture set
- too specialized for memory safety
- problems with structural self-referencing
Scala 3 Effects?
- Refer by type via givens\implicits
- Limit capturing to chosen types
- Don't bother about lifetimes on stack
- Express precise capture sets in types
- Be able to express self-capturing
Path-Dependent
trait Collection{ type Item def get(i: Int): Item } def head(c: Collection): c.Item = c.get(0) val second: (c: Collection) => c.Item = _.get(1)
Capture Set
def foo(x: X^, y: Y): Z^{x} = ???
Capture Set
def foo(x: X^, y: Y): Z^{x} = ???
Capture Set
def foo(x: X^, y: Y): Z^{x} = ???
def foo(x: X, y: Y): A ->{x} B = ???
{} // Nothing {a, b, c} // a | b | c {cap} // Any
X^{} <: X^{a, b} <: X^{c, a, b} <: X^{cap}
Capture Set
SomeClass^{x}
A ->{x} B
{} // Nothing
{a, b, c} // a | b | c
{cap} // Any
X^{} <: X^{a, b} <: X^{c, a, b} <: X^{cap}
type A => B = A ->{cap} B
No Reader
@capability trait Get[+A](val value: A) object Get: def get[A](using get: Get[A]^): A = get.value
No Copying
@capability class State[A](private var value: A) object State: def get[A](using state: State[A]^): A = state.value def set[A](value: A)(using state: State[A]^): Unit = state.value = value def modify[A](f: A => A)(using state: State[A]^): Unit = state.value = f(state.value)
No Copying
@capability class State[A](private var state: A): def get: A = state def set(value: A): Unit = state = value def modify(f: A => A): Unit = state = f(state) object State: def of[A](using state: State[A]): State[A] = state
No Option
final class Aborted extends RuntimeException("", null, false, false) type Abort = CanThrow[Aborted] def getOrElse[T](default: T)(block: => T throws Aborted): T = try block catch case _: Aborted => default
No Comprehension
def lol(using Abort^, Get[String]^, State[Int]^): String = if State.of.get > 10 then Abort.abort State.of.modify(_ + 1) s"Name is ${Get.get}"
No Comprehension
val lol: (Abort^, Get[String]^, State[Int]^) ?-> String = if State.of.get > 10 then Abort.abort State.of.modify(_ + 1) s"Name is ${Get.get}"
Effects?
> (open: FilePath -> FileMode -> {IO, Exception} Handle)
> (parseInt: Text -> {Abort} Int)
> (getLine : Handle -> {IO, Exception} Text)
parseFileLine: Text ->{IO, Exception} Int ->{IO, Exception, Abort} Int
parseFileLines path =
file = open (FilePath path) Read
i -> i + parseInt (getLine file)
Effects!
def open(path: Path, mode: AccessMode)(using IO^, IOExceptions^): Handle
def parseInt(string: String)(using Abort): Int
def getLine(handle: Handle)(using IO^, IOExceptions^): String
def parseFileLines(path: Path)(using IO^, IOExceptions^): Int -> (IO^, IOExceptions^, Abort^) ?-> Int =
val file = open(path, READ)
i => i + parseInt(getLine(file))
@capability trait Stream[A]:
def emit(a: A): Unit
Generators
@capability trait Stream[A]:
def emit(a: A): Unit
def range(from: Int, until: Int)(using stream: Stream[Int]): Unit =
if from < until then
stream.emit(from)
range(from + 1, until)
Generators
@capability trait Stream[A]:
def emit(a: A): Unit
def toList[A](stream: Stream[A]^ ?-> Unit): List[A] =
val builder = List.newBuilder[A]
stream(using new Stream[A]:
def emit(a: A) = builder += a)
builder.result()
Generators
@capability trait Stream[A]:
def emit(a: A): Unit
def toList[A](stream: Stream[A]^ ?-> Unit): List[A] =
val builder = List.newBuilder[A]
given Stream[A] with
def emit(a: A) = builder += a
stream
builder.result()
Generators
@capability trait Stream[A]:
def emit(a: A): Unit
def map[A, B](es: Stream[A]^ ?-> Unit)(f: A -> B)(using bs: Stream[B]^): Unit =
es(using new Stream[A]:
def emit(a: A) = bs.emit(f(a)))
Generators
@capability trait Stream[A]:
def emit(a: A): Unit
def map[A, B](es: Stream[A]^ ?-> Unit)(f: A -> B)(using bs: Stream[B]^): Unit =
es(using new Stream[A]:
def emit(a: A) = bs.emit(f(a)))
def flatMap[A, B](es: Stream[A]^ ?-> Unit)(f: A -> Stream[B]^ ?-> Unit)(using bs: Stream[B]^): Unit =
es(using new Stream[A]:
def emit(a: A) = f(a)(using new Stream[B]:
def emit(b: B) = bs.emit(b)))
Generators
@capability trait Stream[A]:
def emit(a: A): Unit
def map[A, B](es: Stream[A]^ ?-> Unit)(f: A -> B)(using bs: Stream[B]^): Unit =
es(using new Stream[A]:
def emit(a: A) = bs.emit(f(a)))
def flatMap[A, B](es: Stream[A]^ ?-> Unit)(f: A -> Stream[B]^ ?-> Unit)(using bs: Stream[B]^): Unit =
es(using new Stream[A]:
def emit(a: A) = f(a)(using new Stream[B]:
def emit(b: B) = bs.emit(b)))
def filter[A](es: Stream[A]^ ?-> Unit)(p: A -> Boolean)(using bs: Stream[A]^): Unit =
es(using new Stream[A]:
def emit(a: A) = if p(a) then bs.emit(a))
Generators
Stream.toList{ Stream.range(1, 6) Stream.range(10, 16) Stream.range(20, 24) } // List(1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15, 20, 21, 22, 23) Stream.toList{ for(i <- 1 to 5) Stream.emit(i * i) } // List(1, 4, 9, 16, 25)
Generators
Capabilities
- Reusing all the syntactical benefits of implicit (and problems)
- Mark effects via
using
or?->
- No inference, only manual
laborannotations - Easy define, compose handlers as 1st class values
- Use contextual lambda blocks
- Mark effects via
- Embrace old-school imperative programming that's could be much more safe now
- Replace
for
comprehensions with imperative sequencing - Explore Loom's virtual threads for massive concurrency
- Use mutability in capability handlers
- Use CanThrow for exceptions
- Replace
Thank^{you}
Telegram: t.me/odomontois
Discord : https://discord.gg/jrunbnBuNs


Copy of Effects & Capture
By Oleg Nizhnik
Copy of Effects & Capture
- 256