Skip to content

Commit

Permalink
refactored channel to endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
alessiorubicini committed Jun 14, 2024
1 parent cacbc67 commit 92cf4f1
Show file tree
Hide file tree
Showing 13 changed files with 262 additions and 262 deletions.
61 changes: 31 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Swift Sessions

**Swift Sessions** is a comprehensive Swift package that implements binary [session types](https://en.wikipedia.org/wiki/Session_type), providing a robust framework for ensuring safe and structured communication in concurrent systems.
**Swift Sessions** is a Swift package that implements binary [session types](https://en.wikipedia.org/wiki/Session_type), providing a robust framework for ensuring safe and structured communication in concurrent systems.

The library currently supports the following features:
- session type inference (only with closures)
- session types with binary branches
- dynamic linearity checking
- duality constraints on session types
- client/server architecture for session initialization
- session initialization with client/server architecture

## Authors and Acknowledgments

Expand All @@ -17,21 +17,21 @@ This library was developed as part of a Bachelor’s degree thesis project at th

### Programming Styles
This library offers two distinct styles for managing session types:
- **Continuation with Closures**: protocol continuations are passed as closures. This approach makes the flow of logic explicit and easy to follow within the closure context. It's particularly useful for straightforward communication sequences.
- **Continuation with closures**: protocol continuations are passed as closures. This approach makes the flow of logic explicit and easy to follow within the closure context. It's particularly useful for straightforward communication sequences.

```swift
await Session.create { c in
await Session.create { e in
// One side of the communication channel
await Session.recv(from: c) { num, c in
await Session.send(num % 2 == 0, on: c) { c in
await Session.close(c)
await Session.recv(from: e) { num, e in
await Session.send(num % 2 == 0, on: e) { e in
await Session.close(e)
}
}
} _: { c in
// Another side of the communication channel
await Session.send(42, on: c) { c in
await Session.recv(from: c) { isEven, c in
await Session.close(c)
await Session.send(42, on: e) { e in
await Session.recv(from: e) { isEven, e in
await Session.close(e)
}
}
}
Expand All @@ -40,22 +40,22 @@ This library offers two distinct styles for managing session types:
- Pros: Complete type inference of the communication protocol.
- Cons: Nested code structure.

- **Channel Passing for Continuation**: this style involves returning continuation channels from communication primitives. It offers greater flexibility, enabling more modular and reusable code, particularly for complex communication sequences.
- **Continuations with endpoint passing**: this style involves returning continuation endpoints from communication primitives. It offers greater flexibility, enabling more modular and reusable code, particularly for complex communication sequences.

```swift
typealias Communication = Channel<Empty, (Int, Channel<(Bool, Channel<Empty, Empty>), Empty>)>
typealias Protocol = Endpoint<Empty, (Int, Endpoint<(Bool, Endpoint<Empty, Empty>), Empty>)>

// One side of the communication channel
let c = await Session.create { (c: Communication) in
let (num, c1) = await Session.recv(from: c)
let c2 = await Session.send(num % 2 == 0, on: c1)
await Session.close(c2)
let e = await Session.create { (e: Protocol) in
let (num, e1) = await Session.recv(from: e)
let e2 = await Session.send(num % 2 == 0, on: e1)
await Session.close(e2)
}

// Another side of the communication channel
let c1 = await Session.send(42, on: c)
let (isEven, c2) = await Session.recv(from: c1)
await Session.close(c2)
let e1 = await Session.send(42, on: e)
let (isEven, e2) = await Session.recv(from: e1)
await Session.close(e2)
```

- Pros: Simplicity, particularly for avoiding deep indentation.
Expand All @@ -64,7 +64,8 @@ This library offers two distinct styles for managing session types:
Each style provides a unique approach to handling session-based binary communication and comes with its own pros and cons. By supporting both styles, SwiftSessions allows you to choose the best approach (or use both in a hybrid way!) according to your needs and coding preferences.

For additional examples, see the [Tests](Tests) folder.



### Client/Server Architecture

Instead of creating disposable sessions as seen in the previous examples, you can also initialize sessions using a client/server architectural style.
Expand All @@ -73,19 +74,19 @@ A **server** is responsible for creating and managing multiple sessions that can

```swift
// Server side
let server = await Server { c in
await Session.recv(from: c) { num, c in
await Session.send(num % 2 == 0, on: c) { c in
await Session.close(c)
let server = await Server { e in
await Session.recv(from: e) { num, e in
await Session.send(num % 2 == 0, on: e) { e in
await Session.close(e)
}
}
}

// Client side
let c1 = await Client(for: server) { c in
await Session.send(42, on: c) { c in
await Session.recv(from: c) { isEven, c in
await Session.close(c)
let c1 = await Client(for: server) { e in
await Session.send(42, on: e) { e in
await Session.recv(from: e) { isEven, e in
await Session.close(e)
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftSessions/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ public class Client {
/// - Parameters:
/// - server: The server instance to connect to.
/// - closure: The closure to execute on the client's channel after connecting.
init<A, B>(for server: Server<A, B>, _ closure: @escaping (_: Channel<B, A>) async -> Void) async {
init<A, B>(for server: Server<A, B>, _ closure: @escaping (_: Endpoint<B, A>) async -> Void) async {
let channel: AsyncChannel<Sendable> = AsyncChannel()
await server.connect(with: channel)
let c = Channel<B, A>(with: channel)
let c = Endpoint<B, A>(with: channel)
Task {
await closure(c)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftSessions/Empty.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

/// Represents an empty type used for parts of a channel where no message sending or receiving is allowed.
/// Represents an empty type used for parts of an endpoint where no message sending or receiving is allowed.
enum Empty {

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,34 @@
import Foundation
import AsyncAlgorithms

/// Represents a communication channel that enforces session types.
/// Represents a communication endpoint that enforces session types.
///
/// This class provides a safe and linear way to communicate between different parts of your program using session types.
/// It guarantees that the channel is consumed only once, enforcing the expected communication pattern.
/// It guarantees that the endpoint is consumed only once, enforcing the expected communication pattern.
///
/// - Parameters:
/// - A: The type of messages that can be sent on the channel.
/// - B: The type of messages that can be received on the channel.
public final class Channel<A, B> {
/// - A: The type of messages that can be sent to the endpoint.
/// - B: The type of messages that can be received from the endpoint.
public final class Endpoint<A, B> {

/// Underlying asynchronous channel for communication.
public let asyncChannel: AsyncChannel<Sendable>

/// A read-only flag indicating whether the instance has been consumed.
///
/// This property is set to `true` when the channel is consumed and cannot be consumed again.
/// This property is set to `true` when the endpoint is consumed and cannot be consumed again.
private(set) var isConsumed: Bool = false

/// Initializes a new channel with the given asynchronous channel.
/// Initializes a new endpoint with the given asynchronous channel.
/// - Parameter channel: The underlying asynchronous channel for communication.
init(with channel: AsyncChannel<Sendable>) {
self.asyncChannel = channel
}

/// Initializes a new channel from an existing channel
/// - Parameter channel: The channel from which to create the new channel.
init<C, D>(from channel: Channel<C, D>) {
self.asyncChannel = channel.asyncChannel
init<C, D>(from endpoint: Endpoint<C, D>) {
self.asyncChannel = endpoint.asyncChannel
}

/// Deinitializes the channel and ensures it has been consumed.
Expand All @@ -59,7 +59,7 @@ public final class Channel<A, B> {
/// - Throws: a fatal error if the channel has already been consumed.
private func consume() {
guard !isConsumed else {
fatalError("\(self.description) was used twice")
fatalError("\(self.description) was consumed twice")
}
isConsumed = true
}
Expand All @@ -71,7 +71,7 @@ public final class Channel<A, B> {

}

extension Channel where A == Empty {
extension Endpoint where A == Empty {

/// Receives an element from the async channel
///
Expand All @@ -86,7 +86,7 @@ extension Channel where A == Empty {

}

extension Channel where B == Empty {
extension Endpoint where B == Empty {

/// Sends the given element on the async channel
///
Expand All @@ -101,7 +101,7 @@ extension Channel where B == Empty {

}

extension Channel where A == Empty, B == Empty {
extension Endpoint where A == Empty, B == Empty {

/// Resumes all the operations on the underlying asynchronous channel
/// and terminates the communication
Expand Down
6 changes: 3 additions & 3 deletions Sources/SwiftSessions/Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ public class Server<A, B> {

/// Initializes a new server instance that listens for client sessions.
/// - Parameter closure: The closure to execute on the server's channel for each session.
init(_ closure: @escaping (_: Channel<A, B>) async -> Void) async {
init(_ closure: @escaping (_: Endpoint<A, B>) async -> Void) async {
publicChannel = AsyncChannel()
Task {
while true {
for await request in publicChannel {
let asyncChannel = request as! AsyncChannel<Sendable>
let channel = Channel<A, B>(with: asyncChannel)
let endpoint = Endpoint<A, B>(with: asyncChannel)
Task {
await closure(channel)
await closure(endpoint)
}
}
}
Expand Down
58 changes: 29 additions & 29 deletions Sources/SwiftSessions/Session/Session+Closures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,55 +15,55 @@ extension Session {

/// Sends a message on the channel and invokes the specified closure upon completion.
/// - Parameters:
/// - payload: The payload to be sent on the channel.
/// - chan: The channel on which the payload is sent.
/// - payload: The payload to be sent to the endpoint.
/// - endpoint: The endpoint to which the payload is sent.
/// - continuation: A closure to be invoked after the send operation completes.
/// This closure receives the continuation channel for further communication.
static func send<A, B, C>(_ payload: A, on channel: Channel<(A, Channel<B, C>), Empty>, continuation: @escaping (Channel<C, B>) async -> Void) async {
await channel.send(payload)
await continuation(Channel<C, B>(from: channel))
/// This closure receives the continuation endpoint for further communication.
static func send<A, B, C>(_ payload: A, on endpoint: Endpoint<(A, Endpoint<B, C>), Empty>, continuation: @escaping (Endpoint<C, B>) async -> Void) async {
await endpoint.send(payload)
await continuation(Endpoint<C, B>(from: endpoint))
}

/// Receives a message from the channel and invokes the specified closure upon completion.
/// - Parameters:
/// - chan: The channel from which the message is received.
/// - endpoint: The endpoint from which the message is received.
/// - continuation: A closure to be invoked after the receive operation completes.
/// This closure receives the received message and the continuation channel.
static func recv<A, B, C>(from channel: Channel<Empty, (A, Channel<B, C>)>, continuation: @escaping ((A, Channel<B, C>)) async -> Void) async {
let msg = await channel.recv()
await continuation((msg as! A, Channel<B, C>(from: channel)))
/// This closure receives the received message and the continuation endpoint.
static func recv<A, B, C>(from endpoint: Endpoint<Empty, (A, Endpoint<B, C>)>, continuation: @escaping ((A, Endpoint<B, C>)) async -> Void) async {
let msg = await endpoint.recv()
await continuation((msg as! A, Endpoint<B, C>(from: endpoint)))
}

/// Offers a choice between two branches on the given channel, and executes the corresponding closure based on the selected branch.
/// - Parameters:
/// - channel: The channel on which the choice is offered. This channel expects a value indicating the selected branch (`true` for the first branch, `false` for the second branch).
/// - side1: The closure to be executed if the first branch is selected. This closure receives a channel of type `Channel<A, B>`.
/// - side2: The closure to be executed if the second branch is selected. This closure receives a channel of type `Channel<C, D>`.
static func offer<A, B, C, D>(on channel: Channel<Empty, Or<Channel<A, B>, Channel<C, D>>>, _ side1: @escaping (Channel<A, B>) async -> Void, or side2: @escaping (Channel<C, D>) async -> Void) async {
let bool = await channel.recv() as! Bool
/// - endpoint: The endpoint on which the choice is offered. This endpoint expects a value indicating the selected branch (`true` for the first branch, `false` for the second branch).
/// - side1: The closure to be executed if the first branch is selected. This closure receives a endpoint of type `Endpoint<A, B>`.
/// - side2: The closure to be executed if the second branch is selected. This closure receives a endpoint of type `Endpoint<C, D>`.
static func offer<A, B, C, D>(on endpoint: Endpoint<Empty, Or<Endpoint<A, B>, Endpoint<C, D>>>, _ side1: @escaping (Endpoint<A, B>) async -> Void, or side2: @escaping (Endpoint<C, D>) async -> Void) async {
let bool = await endpoint.recv() as! Bool
if bool {
await side1(Channel<A, B>(from: channel))
await side1(Endpoint<A, B>(from: endpoint))
} else {
await side2(Channel<C, D>(from: channel))
await side2(Endpoint<C, D>(from: endpoint))
}
}

/// Selects the left branch on the given channel and executes the provided continuation closure.
/// Selects the left branch on the given endpoint and executes the provided continuation closure.
/// - Parameters:
/// - channel: The channel on which the left branch is selected. This channel sends a value indicating the left branch selection (`true`).
/// - continuation: A closure to be executed after the left branch is selected. This closure receives a channel of type `Channel<B, A>`.
static func left<A, B, C, D>(_ channel: Channel<Or<Channel<A, B>, Channel<C, D>>, Empty>, continuation: @escaping (Channel<B, A>) async -> Void) async {
await channel.send(true)
await continuation(Channel<B, A>(from: channel))
/// - endpoint: The channel on which the left branch is selected.
/// - continuation: A closure to be executed after the left branch is selected. This closure receives a endpoint of type `Endpoint<B, A>`.
static func left<A, B, C, D>(_ endpoint: Endpoint<Or<Endpoint<A, B>, Endpoint<C, D>>, Empty>, continuation: @escaping (Endpoint<B, A>) async -> Void) async {
await endpoint.send(true)
await continuation(Endpoint<B, A>(from: endpoint))
}

/// Selects the right branch on the given channel and executes the provided continuation closure.
/// - Parameters:
/// - channel: The channel on which the right branch is selected. This channel sends a value indicating the right branch selection (`false`).
/// - continuation: A closure to be executed after the right branch is selected. This closure receives a channel of type `Channel<D, C>`.
static func right<A, B, C, D>(_ channel: Channel<Or<Channel<A, B>, Channel<C, D>>, Empty>, continuation: @escaping (Channel<D, C>) async -> Void) async {
await channel.send(false)
await continuation(Channel<D, C>(from: channel))
/// - endpoint: The endpoint on which the right branch is selected.
/// - continuation: A closure to be executed after the right branch is selected. This closure receives a endpoint of type `Endpoint<D, C>`.
static func right<A, B, C, D>(_ endpoint: Endpoint<Or<Endpoint<A, B>, Endpoint<C, D>>, Empty>, continuation: @escaping (Endpoint<D, C>) async -> Void) async {
await endpoint.send(false)
await continuation(Endpoint<D, C>(from: endpoint))
}

}
Loading

0 comments on commit 92cf4f1

Please sign in to comment.