Skip to content

romac/choreo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

48 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Continuous Integration codecov

Choreo

A library for choreographic programming in Scala 3.

The implementation is based on the paper HasChor: Functional Choreographic Programming for All by Gan Shen, Shun Kashiwa and Lindsey Kuper, and its Haskell implementation.

Build

Choreo uses Mill as its build tool.

# Compile
./mill __.compile

# Run tests
./mill core.test

# Run examples
./mill examples.runMain choreo.examples.kv
./mill examples.runMain choreo.examples.bookseller

Example

import cats.effect.IO
import cats.effect.IO.asyncForIO
import cats.effect.kernel.Ref
import cats.syntax.all.*

import choreo.backend.Backend

type State = Map[String, String]

enum Request:
  case Get(key: String)
  case Put(key: String, value: String)

type Response = Option[String]

val client: "client" = "client"
val server: "server" = "server"

def main: IO[Unit] =
  for
    backend   <- Backend.local(List(client, server))
    clientTask = choreo.project(backend, client)
    serverTask = choreo.project(backend, server)
    _         <- (clientTask, serverTask).parTupled
  yield ()

def choreo: Choreo[IO, Unit] =
  for
    stateS <- server.locally(Ref.of[IO, State](Map.empty))
    _      <- step(stateS).foreverM
  yield ()

def step(stateS: Ref[IO, State] @@ "server"): Choreo[IO, Unit] =
  for
    reqC <- client.locally(readRequest)
    resC <- kvs(reqC, stateS)
    _    <- client.locally:
              resC.!.fold(IO.println("Key not found")):
                IO.print("> ") *> IO.println(_)
  yield ()

def kvs(
    reqC: Request @@ "client",
    stateS: Ref[IO, State] @@ "server"
): Choreo[IO, Response @@ "client"] =
  for
    reqS <- client.send(reqC).to(server)
    resS <- server.locally(handleRequest(reqS.!, stateS.!))
    resC <- server.send(resS).to(client)
  yield resC

def handleRequest(
    req: Request,
    state: Ref[IO, State]
): IO[Response] =
  req match
    case Request.Get(key) =>
      state.get.map(_.get(key))

    case Request.Put(key, value) =>
      state.update(_.updated(key, value)).as(Some(value))

// Requests are either:
//    GET key
//    PUT key value
def readRequest: IO[Request] =
  for
    _    <- IO.print("> ")
    line <- IO.readLine
    req  <- line.split(" ") match
              case Array("GET", key) =>
                IO.pure(Request.Get(key))

              case Array("PUT", key, value) =>
                IO.pure(Request.Put(key, value))

              case _ =>
                IO.raiseError(new Exception("Invalid request"))
  yield req

License

This library is released under the same license as HasChor, namely the BSD-3 license.

Please see the LICENSE within this repository for the full text of the license.

About

Choreographic programming in Scala

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Contributors