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