Best practices for stacking context types?

Context functions and type aliases allow us to express dependency on context as a result type as opposed to a function parameter. This has some very nice properties, but it begins to fall apart in certain situations. To start from the beginning, here’s an example of a context type:

type Executee[A] = ExecutionContext ?=> A

val executee: Executee[Future[Int]] = Future("3").map(_.toInt)

You will notice that the context type (I’m tending have these end with the -ee suffix, indicating receiver of execution in this case) is a higher kinded type than the original context input in order to allow it to describe many types of outputs. This hits a snag when the input context is higher kinded too:

type Decodee[A,B] = Decoder[A] ?=> B
type Encodee[A, B] = Encoder[A] ?=> B

val idem: Encodee[String, Decodee[String, String]] = decode[String]("hello world".asJson).getOrElse(???)

I am trying to brainstorm a better way to stack these context types on each other. What I’ve come up with so far is a bit ugly, and I was wondering if someone could find a better way:

type Decodee[F[_]] = [B] =>> Decoder[B] ?=> F[B]
type Encodee[F[_]] = [B] =>> Encoder[B] ?=> F[B]
type Res[A] = [B] =>> A

val idem: Encodee[Decodee[Res[Int]]][String] = decode[String]("5".asJson).getOrElse(???).toInt

This allows for an input type that is passed in outside the stacked type (String in this case) which is passed through the context dependency chain, and a result type. It still looks unwieldy to me though. Any thoughts?

Without type aliases and stacking maybe you could use intersection

idem: Decoder[String] & Encoder[String] ?=> Int

Intersection only works for those two types since the intersection of them exists (Codec). Most of the types I want to use this way have no intersection, nor do I want to make one.