I'm writing a small library that would allow me to handle Sockets in a generic fashion.
I basically want to define a Socket such as it would define Emitting & Receiving types which would be the events we want it emit and receive, respectively.
Now, I like using enums for this sort of functionality, where I could define the Emitting type like such:
struct MySocket {
enum Emitting {
case send(message: String)
case join(room: String)
var responseType: <?> {
switch self {
case .send: return nil
case .join: return Room.self
}
}
struct Room {
let id: String
let participants: Int
}
}
}
You'll notice that I also defined a Room type, which is data that I know the socket should return when I emit .join(room: "room_uuid"). There could also be other such types I could define for any future event.
The idea is that I'd then have my generic Socket implementation which would have a convenience emit(_:) function that would be called as such:
let socket = MySocket()
socket.emit(.join(room: "room_uuid") { data in
// type(of: data) == Emitting.Room
}
Is this at all feasible ? I know I can define a single associatedtype which would allow me to do
protocol Event {
associatedtype Response
}
struct Socket<Emitting: Event, Receiving: Event> {
func emit(_ event: Emitting, handler: @escaping (Emitting.Response) -> Void) {
// magic
}
}
but that then prevents me from easily defining multiple events with multiple response types.
I don't know if this actually makes sense? Maybe all I need to do is define functions for each event rather than types
then I have control over what response type I send back in the handler...
Kentzo
(Ilya Kulakov)
2
I think the closest you can get is another enum with the same cases but response objects as associated types instead:
enum EmittingEvent {
case send,
case join(Emitting.Room)
}
socket.emit(.join(room: "room_uuid") { event in
guard case let .join(data) = event else { return /* or throw */ }
// type(of: data) == Emitting.Room
}
Alternatively drop enum for the types of messages and use structs sharing a common protocol that provides an associated type for the response value.
I was thinking of doing something like that, but that kind of defeats the point altogether, since I'm trying to achieve type safety 
Lantua
4
"type safety" is not all powerful. If you want emit signature to look something like this
func emit(_ request: Emitting, respond: (XX) -> ())
Then the response XX can only be based on the fact that request is Emitting, not that it is specifically send or join.
You could make Emitting a protocol instead, which would let you push some information up into the type-system level
protocol Emitting {
associatedtype Response
}
struct Send {
typealias Response = Never
}
struct Join {
typealias Response = Room
}
PS
Your emit call has unbalanced ()
1 Like