Skip to content

Commit

Permalink
Backport service interpreter (address #1111) (#1118)
Browse files Browse the repository at this point in the history
  • Loading branch information
miguel-vila authored Jul 25, 2023
1 parent 0edb9e6 commit a0186ae
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 0.17.13

- Backports Service interpreter logic introduced in [#908](https://github.com/disneystreaming/smithy4s/pull/908).

# 0.17.12

* Remove reserved types in https://github.com/disneystreaming/smithy4s/pull/1052
Expand Down
5 changes: 5 additions & 0 deletions modules/core/src-2/kinds/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,15 @@ package object kinds {
type Kind1[F[_]] = {
type toKind2[E, O] = F[O]
type toKind5[I, E, O, SI, SO] = F[O]
type handler[I, E, O, SI, SO] = I => F[O]
}

type Kind2[F[_, _]] = {
type toKind5[I, E, O, SI, SO] = F[E, O]
type handler[I, E, O, SI, SO] = I => F[E, O]
}

type Kind5[F[_, _, _, _, _]] = {
type handler[I, E, O, SI, SO] = I => F[I, E, O, SI, SO]
}
}
6 changes: 6 additions & 0 deletions modules/core/src-3/kinds/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,16 @@ package object kinds {
type Kind1[F[_]] = {
type toKind2[E, O] = F[O]
type toKind5[I, E, O, SI, SO] = F[O]
type handler[I, E, O, SI, SO] = I => F[O]
}

type Kind2[F[_, _]] = {
type toKind5[I, E, O, SI, SO] = F[E, O]
type handler[I, E, O, SI, SO] = I => F[E, O]
}

type Kind5[F[_, _, _, _, _]] = {
type handler[I, E, O, SI, SO] = I => F[I, E, O, SI, SO]
}

}
112 changes: 110 additions & 2 deletions modules/core/src/smithy4s/Service.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,77 @@ import kinds._
* metaprogramming.
*/
trait Service[Alg[_[_, _, _, _, _]]] extends FunctorK5[Alg] with HasId {
/**
* A datatype (typically a sealed trait) that reifies an operation call within
* a service. It essentially captures the input and type indexes that the operation
* deals with. It also typically captures an input value.
*
* It is possible to think of Operation as an "applied [[Endpoint]]",
* or a "call to an [[Endpoint]]".
*
* @tparam I: the input of the operation
* @tparam E: the error type associated to the operation (typically represented as a sealed-trait)
* @tparam O: the output of the operation
* @tparam SI: the streamed input of an operation. Operations can have unary components and streamed components.
* For instance, an http call can send headers (unary `I`) and a stream of bytes (streamed `SI`) to the server.
* @tparam SO: the streamed output of the operation.
*/
type Operation[I, E, O, SI, SO]

/**
* An endpoint is the set of schemas tied to types associated with an [[Operation]].
* It has a method to wrap the input in an operation instance I => Operation[I, E, O, SI, SO].
*
* You can think of the endpoint as a "template for an [[Operation]]". It contains everything
* needed to decode/encode operation calls to/from low-level representations (like http requests).
*/
type Endpoint[I, E, O, SI, SO] = smithy4s.Endpoint[Operation, I, E, O, SI, SO]

/**
* This is a polymorphic function that runs an instance of an operation and produces an effect F.
*/
type Interpreter[F[_, _, _, _, _]] = PolyFunction5[Operation, F]
type FunctorInterpreter[F[_]] = PolyFunction5[Operation, kinds.Kind1[F]#toKind5]
type BiFunctorInterpreter[F[_, _]] = PolyFunction5[Operation, kinds.Kind2[F]#toKind5]

/**
* An interpreter specialised for effects of kind `* -> *`, like Try or monofunctor IO.
*/
type FunctorInterpreter[F[_]] = Interpreter[kinds.Kind1[F]#toKind5]

/**
* An interpreter specialised for effects of kind `* -> (*, *)`, like Either or bifunctor IO.
*/
type BiFunctorInterpreter[F[_, _]] = Interpreter[kinds.Kind2[F]#toKind5]

/**
* A polymorphic function that can take an Endpoint (associated to this service) and
* produces an handler for it, namely a function that takes the input type of the
* operation, and produces an effect.
*/
type EndpointCompiler[F[_, _, _, _, _]] = PolyFunction5[Endpoint, Kind5[F]#handler]

/**
* A [[EndpointCompiler]] specialised for effects of kind `* -> *`, like Try or monofunctor IO
*/
type FunctorEndpointCompiler[F[_]] = EndpointCompiler[Kind1[F]#toKind5]

/**
* A [[EndpointCompiler]] specialised for effects of kind `* -> (*, *)`, like Either or bifunctor IO
*/
type BiFunctorEndpointCompiler[F[_, _]] = EndpointCompiler[Kind2[F]#toKind5]

/**
* A short-hand for algebras that are specialised for effects of kind `* -> *`.
*
* NB: this alias should be used in polymorphic implementations. When using the Smithy4s
* code generator, equivalent aliases that are named after the service are generated
* (e.g. `Weather` corresponding to `WeatherGen`).
*/
type Impl[F[_]] = Alg[kinds.Kind1[F]#toKind5]

/**
* A short-hand for algebras that are specialised for effects of kind `* -> (*, *)`.
* This is meant to be used in userland, e.g: {{{ val myService = MyService.ErrorAware[Either] }}}
*/
type ErrorAware[F[_, _]] = Alg[kinds.Kind2[F]#toKind5]

val service: Service[Alg] = this
Expand All @@ -56,6 +121,49 @@ trait Service[Alg[_[_, _, _, _, _]]] extends FunctorK5[Alg] with HasId {
def apply[I, E, O, SI, SO](op: Operation[I,E,O,SI,SO]): Endpoint[I,E,O,SI,SO] = endpoint(op)._2
}

/**
* Given a generic way to turn an endpoint into some handling function (like `I => F[I, E, O, SI, SO]`), this method
* takes care of the logic necessary to produce an interpreter that takes an Operation associated
* to the service and routes it to the correct function, returning the result.
*/
final def interpreter[F[_, _, _, _, _]](compiler: EndpointCompiler[F]) : Interpreter[F] = new Interpreter[F]{
val cached = compiler.unsafeCacheBy(endpoints.map(Kind5.existential(_)), identity)
def apply[I, E, O, SI, SO](operation: Operation[I, E, O, SI, SO]): F[I, E, O, SI, SO] = {
val (input, ep) = endpoint(operation)
cached(ep).apply(input)
}
}

/**
* A monofunctor-specialised version of [[interpreter]]
*/
final def functorInterpreter[F[_]](compiler: FunctorEndpointCompiler[F]): FunctorInterpreter[F] = interpreter[Kind1[F]#toKind5](compiler)

/**
* A bifunctor-specialised version of [[interpreter]]
*/
final def bifunctorInterpreter[F[_, _]](compiler: BiFunctorEndpointCompiler[F]): BiFunctorInterpreter[F] = interpreter[Kind2[F]#toKind5](compiler)


/**
* A function that takes an endpoint compiler and produces an Algebra (typically an instance of the generated interfaces),
* backed by an interpreter.
*
* This is useful for writing generic functions that result in the instantiation of a client instance that abides by
* the service interface.
*/
final def algebra[F[_, _, _, _, _]](compiler: EndpointCompiler[F]) : Alg[F] = fromPolyFunction(interpreter(compiler))

/**
* A monofunctor-specialised version of [[algebra]]
*/
final def impl[F[_]](compiler: FunctorEndpointCompiler[F]) : Impl[F] = algebra[Kind1[F]#toKind5](compiler)

/**
* A monofunctor-specialised version of [[algebra]]
*/
final def errorAware[F[_, _]](compiler: BiFunctorEndpointCompiler[F]) : ErrorAware[F] = algebra[Kind2[F]#toKind5](compiler)

}

object Service {
Expand Down

0 comments on commit a0186ae

Please sign in to comment.