Skip to content

Commit

Permalink
compile snippets
Browse files Browse the repository at this point in the history
  • Loading branch information
hearnadam committed Nov 6, 2024
1 parent c3c157b commit ece2ce0
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 45 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ project/local-plugins.sbt
.sbt-scripted/
local.sbt

# scala-cli
.scala-build/

# Bloop
.bsp

Expand Down
21 changes: 21 additions & 0 deletions slides/snippets/abort.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//> using dep io.getkyo::kyo-core:0.13.2

import kyo.*

case class User(email: String)
object User extends KyoApp:
import UserError._
enum UserError:
case InvalidEmail
case AlreadyExists

def from(email: String): User < Abort[UserError] =
if !email.contains('@') then Abort.fail(InvalidEmail)
else User(email)

val x: Unit < IO =
Abort
.run(from("[email protected]"))
.map:
case Result.Success(user) => Console.println(s"Success! $user")
case Result.Fail(InvalidEmail) => Console.println("Bad email!")
28 changes: 28 additions & 0 deletions slides/snippets/env.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//> using dep io.getkyo::kyo-core:0.13.2

import kyo.*

case class Coordinates(latitude: Double, longitude: Double)
case class Reading()
abstract class Sensor:
def read: Reading < IO = Reading()
object Sensor extends Sensor

abstract class Drone:
def fly(coordinates: Coordinates): Unit < IO = IO(println(s"$coordinates"))
object Drone extends Drone

abstract class Weather:
def record(coordinates: Coordinates): Reading < IO

object Weather extends KyoApp:
val live: Weather < (Env[Drone] & Env[Sensor]) =
for
drone <- Env.get[Drone]
sensor <- Env.get[Sensor]
yield new Weather:
def record(coordinates: Coordinates): Reading < IO =
drone.fly(coordinates).andThen(sensor.read)

run:
Env.runTypeMap(TypeMap(Drone, Sensor))(live).map(_.record(Coordinates(0, 0)))
19 changes: 19 additions & 0 deletions slides/snippets/io.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//> using dep io.getkyo::kyo-core:0.13.2

import kyo.*

case class Person(name: String, age: Int)
class SQL[A](sql: String) extends AnyVal

extension (sc: StringContext)
def sql[A](args: Any*): SQL[A] = new SQL(sc.s(args*))

object DB:
private val local = Local.init(())
def query[A](sql: SQL[A]): Chunk[A] < IO = local.get.map(_ => IO(println(s"querying $sql"))).as(Chunk.empty)

object MyApp extends KyoApp:
val x: Chunk[Person] < Any =
import AllowUnsafe.embrace.danger
IO.Unsafe.run(DB.query(sql"select * from person limit 5"))
run {42}
119 changes: 74 additions & 45 deletions slides/talk.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ autoscale: false
# [fit] Introduction to Kyo
## Adam Hearn

<!-- ---
# Agenda
- What is Kyo?
- Algebraic Effects
- Kyo
- Syntax + Effects
- Quirks + Features -->

---

# What is Kyo?
Expand Down Expand Up @@ -91,88 +100,108 @@ val _: String < IO = IO("Hello scala.io!")
* `File < (IO & Resource)`'

---
# IO: Side-Effect Suspension

Kyo enables labeling side effects via `IO`:
#[fit] Effects!

---
### IO: Side-Effect Suspension

```scala
object DB:
def run(query: SQL[Result]): Result < IO = ???
def query[A](sql: SQL[A]): Chunk[A] < IO = ???

object MyApp:
val _: Chunk[Person] < Any =
object Query:
val _: Chunk[Person] < Any =
import AllowUnsafe.embrace.danger
IO.Unsafe.run(DB.run(sql"select * from person"))
IO.Unsafe.run(DB.query(sql"select * from person limit 5"))
```

- `IO` must be handled individually (`IO.Unsafe.run`)
- Unsafe APIs require an `AllowUnsafe` evidence
- The above expression is not fully evaluated and may be pending further suspensions.
- `IO` are handled individually (`IO.Unsafe.run`)
- Unsafe APIs require an `AllowUnsafe` evidence

^ The above expression is not fully evaluated and may be pending further suspensions.
^ You might note the `< Any` remaining.
^ These are any pending suspensions which are not yet handled or labeled.
^ Suspensions can be introduced internally by Kyo, or usages of APIs which introduce suspensions, eg Local.

---
# Abort: Short Circuit
## Abort: Short Circuit
```scala
object User:
case class User(email: String)
case class User(email: String)
object User extends KyoApp:
import UserError._
enum UserError:
case InvalidEmail
case AlreadyExists

def from(email: String): User < Abort[UserError] =
if !email.contains('@') then Abort.fail(Invalid)
else User(names.head, names.tail)

val x: Unit < IO =
Abort.run(from("[email protected]")).map:
case Result.Success(user) => Console.println(s"Success! $user")
case Result.Fail(Invalid) => Console.println(s"Bad email!")

def from(email: String): User < Abort[UserError] =
if !email.contains('@') then Abort.fail(InvalidEmail)
else User(email)

val x: Unit < IO =
Abort
.run(from("[email protected]"))
.map:
case Result.Success(user) => Console.println(s"Success! $user")
case Result.Fail(InvalidEmail) => Console.println("Bad email!")
```

^ Abort enables ZIO style short circuiting

---
# Env: Dependency Injection
```
// TODO Fix code
trait DB:
def apply(id: Int): String < IO
val program: Record < (Env[DB] & IO) =
for
config <- Env.get[DB]
result <- IO(s"Connecting to ${config.url}")
yield result
// Usage
val config = Config("http://example.com")
val result = program.provideEnv(config).eval
## Env: Dependency Injection

```scala
abstract class Weather:
def record(coordinates: Coordinates): Reading < IO

object Weather:
val live: Weather < (Env[Drone] & Env[Sensor]) =
for
drone <- Env.get[Drone]
sensor <- Env.get[Sensor]
yield new Weather:
def record(coordinates: Coordinates): Reading < IO =
drone.fly(coordinates).andThen(sensor.read)
```

---
# Kyo: Effect Widening
## Kyo: Effect Widening

```scala
val a: Int < IO = IO(42)
val b: Int < (IO & Abort[Exception]) = a
val c: Int < (IO & Async & Abort[Exception]) = b
val a: String < IO = "Hello"
val b: String < (IO & Abort[Exception]) = a
val c: String < (IO & Abort[Exception] & Resource) = b
```

- Computations can be widened to include more effects
- Allows for flexible and composable code
- Plain values can be widened to Kyo computation
- Values aren't suspended & don't allocate [^1]
- Widened values are not suspended
- Widened values do not allocate [^1]

[^1]: Primitives widened to Kyo will box as Scala 3 does not support proper specialization

---
# Kyo: Branching
# Kyo: Unnested encoding

```scala
object Write:
def apply[S](v: String < S): Unit < (S & IO) =
v.map(Buffer.write(_, "output.txt"))

^ TODO
object MyApp:
val value: Unit < IO = Write("Hello, World!")
val effect: Unit < (IO & Abort[IOException]) = Write(Console.readLine)
val mapped: Unit < (IO & Abort[IOException]) = value.map(_ => effect)
```

---
* Widening pure values as effects enables fluent composition.
* Functions can be defined to accept effects, and values can be passed in.
* `F.pure`/`ZIO.succeed` no more!

# Kyo: Flattening
---
# Kyo: Unnested encoding

- Effects can be easily combined using `map` and `flatMap`
- Resulting type includes all unique pending effects
Expand Down

0 comments on commit ece2ce0

Please sign in to comment.