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

\frac{x : T \in \Gamma}{ \Gamma \vdash x : T } var
\frac{\Gamma \vdash f : A \to B \qquad \Gamma \vdash v : A}{ \Gamma \vdash f(v) : B } app
\frac{\Gamma, x: A \vdash e : B}{ \Gamma \vdash \lambda x: A\;.\;e : A \to B } lam
\frac{\Gamma, x: A \vdash b : B \qquad \Gamma \vdash e: A}{ \Gamma \vdash let\;x: A = e\;in\;b: B }let

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

\frac{x : T \in \Gamma}{ \Gamma \vdash x : T } var

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)     	
     }
  }
}
\frac{\Gamma \vdash f : A \to B \qquad \Gamma \vdash v : A}{ \Gamma \vdash f(v) : B } app

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

\frac{\Gamma, x: A \vdash e : B}{ \Gamma \vdash \lambda x: A\;.\;e : A \to B } lam

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)     	
     }
  }
}
\frac{\Gamma, x: A \vdash b : B \qquad \Gamma \vdash e: A}{ \Gamma \vdash let\;x: A = e\;in\;b: B }let
let\;x:A=e\;in\;b \sim (lam\;x:A . b)(e)

x = productIds

Free

Free([var]\;x) = \{x\} \\ Free([app]\;f\;e) = Free(f) \cup Free(e) \\ Free([lambda]\;x:T.e) = Free(e) \setminus \{x\}

Free

Free([var]\;x) = \{x\} \\ Free([app]\;f\;e) = Free(f) \cup Free(e) \\ Free([lambda]\;x:T.e) = Free(e) \setminus \{x\}
id

Free(e) = {id}

Free

Free([var]\;x) = \{x\} \\ Free([app]\;f\;e) = Free(f) \cup Free(e) \\ Free([lambda]\;x:T.e) = Free(e) \setminus \{x\}
customerRepo.getById(id)

Free(e) = {customerRepo, id}

Free

Free([var]\;x) = \{x\} \\ Free([app]\;f\;e) = Free(f) \cup Free(e) \\ Free([lambda]\;x:T.e) = Free(e) \setminus \{x\}
{id : CustomerId => 
     	customerRepo.getById(id) 
}  

Free(e) = {customerRepo}

Free

Free([var]\;x) = \{x\} \\ Free([app]\;f\;e) = Free(f) \cup Free(e) \\ Free([lambda]\;x:T.e) = Free(e) \setminus \{x\}
val productIds: List[ProductIds] = 
	order.items.flatMap(_.productIds)

productIds.traverse{id : CustomerId => 
	customerRepo.getById(id)     	
}

Free(e) = {order, customerRepo}

Free

Free([var]\;x) = \{x\} \\ Free([app]\;f\;e) = Free(f) \cup Free(e) \\ Free([lambda]\;x:T.e) = Free(e) \setminus \{x\}
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

Free([var]\;x) = \{x\} \\ Free([app]\;f\;e) = Free(f) \cup Free(e) \\ Free([lambda]\;x:T.e) = Free(e) \setminus \{x\}
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)

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

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]

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

\frac{x : T \in \Gamma}{ \Gamma; \epsilon \vdash x : T } var
\frac{ \Gamma; \epsilon \vdash f : A \to_{\{\epsilon_1\}} B \qquad \Gamma; \epsilon \vdash v : A \qquad \epsilon_1 \subset \epsilon }{ \Gamma; \epsilon \vdash f(v) : B } app
\frac{\Gamma, x: A ;\epsilon \vdash e : B}{ \Gamma; \zeta \vdash \lambda x: A\;.\;e : A \to_{\epsilon} B } lam
\frac{ \Gamma; \epsilon, \zeta \vdash e : A \qquad \Gamma; \epsilon \vdash h : Handler_\zeta(A) }{ \Gamma; \epsilon \vdash handle\ e \ with\ h : A } handle

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)]
)

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


class MyState private (private var count: Int)

object MyState {
    def count(using state: MyState): Int        = state.count
    def increment()(using state: MyState): Unit = state.count += 1
    def withState[T](actions: MyState ?=> T): T = {
        given MyState(0)
        actions
    }
}

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

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

\frac{x : T \in \Gamma \cup \Delta}{ \Gamma; \Delta \vdash x : T } var
\frac{ \Gamma; \Delta \vdash f : (A, \Delta') \to B \quad \Gamma; \Delta \vdash v : A \quad \Gamma; \Delta \vdash \delta': \Delta' }{ \Gamma; \Delta \vdash f(v, \delta') : B } app
\frac{\Gamma, x: A ;\Delta \vdash e : B}{ \Gamma; \Epsilon \vdash \lambda x: A\;.\;e : (A; \Delta) \to B } lam

Unique Lambda

\frac{x : T \in \Gamma \cup \Delta}{ \Gamma; \Delta \vdash x : T } var
\frac{\Gamma, x: A ;\Delta \vdash e : B}{ \Gamma; \Epsilon \vdash \lambda x: A\;.\;e : (A; \Delta) \to B } lam

Effectful Lambda

\frac{x : T \in \Gamma}{ \Gamma; \epsilon \vdash x : T } var
\frac{ \Gamma; \epsilon \vdash f : A \to_{\{\epsilon_1\}} B \qquad \Gamma; \epsilon \vdash v : A \qquad \epsilon_1 \subset \epsilon }{ \Gamma; \epsilon \vdash f(v) : B } app
\frac{\Gamma, x: A ;\epsilon \vdash e : B}{ \Gamma; \zeta \vdash \lambda x: A\;.\;e : A \to_{\epsilon} B } lam
\frac{ \Gamma; \Delta \vdash f : (A, \Delta') \to B \quad \Gamma; \Delta \vdash v : A \quad \Gamma; \Delta \vdash \delta': \Delta' }{ \Gamma; \Delta \vdash f(v, \delta') : B } app

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

\frac{}{ \Gamma, x: T \vdash x : T } var
\frac{\Gamma \vdash f : A \to B \qquad \Delta \vdash v : A}{ \Gamma, \Delta \vdash f(v) : B } app
\frac{\Gamma, x: A \vdash e : B}{ \Gamma \vdash \lambda x: A\;.\;e : A \to B } lam

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)

Path-Dependent

trait Pair{
    type First
    type Second
    val first: First
    val second: Second

    override def toString = s"($first, $second)"
}


def rotateLeft(p: Pair{type First <: Pair}): Pair{
        type First = p.first.First
        type Second = Pair{
                type First = p.first.Second
                type Second = p.Second
        } 
    } = ???

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 labor annotations
    • Easy define, compose handlers as 1st class values
    • Use contextual lambda blocks
  • 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

Thank^{you}

Telegram: t.me/odomontois

Discord : https://discord.gg/jrunbnBuNs

Effects & Capture

By Oleg Nizhnik

Effects & Capture

  • 258