Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala using shapeless to combine higher kinded coproducts over a natural transformation

Given a set of Adts which have two distinct sub sets For example:

sealed trait Domain[Y]
sealed trait Command[Y] extends Domain[Y]
sealed trait Query[Y] extends Domain[Y]

case class Add(value:String) extends Command[Ack]
case class Remove(value:String) extends Command[Ack]
case class Exists(value:String) extends Query[Boolean]
case object List extends Query[List[String]]

Now suppose I have two Natural transformations, for some arbitrary Monad M[_]:

val commandHandler:Command ~> M
val queryExecutor:Query ~> M

I wish to somehow combine these two natural transformations into a single transformation:

val service:Domain ~> M = union(commandHandler, queryExecutor)

However we are struggling to get off the starting block with having higherkinded coproducts. Even a point in the right direction would be helpful at this stage.

like image 565
J Pullar Avatar asked Aug 13 '15 07:08

J Pullar


1 Answers

Okay this is a very old question, but nowadays, cats for example provides Coproduct and a method or on NaturalTransformation:

trait NaturalTransformation[F[_], G[_]] extends Serializable { self =>
  def or[H[_]](h: H ~> G): Coproduct[F, H, ?] ~> G = ???
}

So you could use this to do (using kind-projector for the type lambda ?)

val combine: Coproduct[Command,Query,?] ~> M = commandHandler.or(queryExecutor)

EDIT: Here is a full example that also defines union (using Id instead of M for type checking):

import cats._
import cats.data._

trait Ack
sealed trait Domain[Y]
sealed trait Command[Y] extends Domain[Y]
sealed trait Query[Y] extends Domain[Y]

case class Add(value:String) extends Command[Ack]
case class Remove(value:String) extends Command[Ack]
case class Exists(value:String) extends Query[Boolean]
case object List extends Query[List[String]]

def commandHandler:Command ~> Id = ???
def queryExecutor:Query ~> Id = ???

def union: Domain ~> Coproduct[Command,Query,?] = new (Domain ~> Coproduct[Command,Query,?]) {
  def apply[A](fa: Domain[A]): Coproduct[Command,Query,A] = fa match {
    case command: Command[A] => Coproduct.left(command)
    case query: Query[A] => Coproduct.right(query)
  }
}

def result: Domain ~> Id = commandHandler.or(queryExecutor).compose(union)

or in case you want to avoid the intermediate Coproduct:

def unionDirect[M[_]](cmd: Command ~> M, qry: Query ~> M): Domain ~> M =
  new (Domain ~> M) {
    def apply[A](fa: Domain[A]): M[A] = fa match {
      case command: Command[A] => cmd(command)
      case query: Query[A] => qry(query)
    }
}

def resultDirect: Domain ~> Id = unionDirect(commandHandler,queryExecutor)
like image 120
Markus1189 Avatar answered Oct 23 '22 00:10

Markus1189