-
-
Save ubourdon/0b17f0b43a68c89b74e1 to your computer and use it in GitHub Desktop.
| /** In fact I try to write parametric Eventsourcing apply function | |
| * And which allow me to have a parametric function for CommandHandler[D] too. | |
| */ | |
| trait ApplyTo { | |
| def applyTo[E, S](startingState: S)(events: List[E])(implicit de: DomainEvent.Aux[E, S]): S = { | |
| events.foldLeft(startingState) { (currentState, event) => de.apply(currentState, event) } | |
| } | |
| } | |
| @implicitNotFound("No member of type class DomainEvent found for type ${A}") | |
| trait DomainEvent[A] { | |
| type State | |
| def apply(currentState: State, event: A): State | |
| } | |
| object DomainEvent { | |
| //def apply[T: DomainEvent]: DomainEvent[T] = implicitly[DomainEvent[T]] | |
| type Aux[A, S] = DomainEvent[A] { type State = S } | |
| implicit object TicketEventTypeclass extends DomainEvent[TicketEvent] { | |
| type State = TicketModel.State | |
| override def apply(currentState: State, event: TicketEvent): State = ticket.models.apply(currentState, event) | |
| } | |
| } |
| trait TicketCommandHandler extends ApplyTo { | |
| type DomainEither[DomainError, State] = EitherT[Future, DomainError, State] | |
| type NextState = TicketModel.Ticket | |
| type TicketEither[A] = DomainEither[TicketError, A] // <: TicketModel.State | |
| def CommandHandler(command: TicketCommand)(implicit eventReader: EventStream.Id => Future[List[TicketEvent]] = Eventstore.read[TicketEvent], | |
| eventWriter: (EventStream.Id, List[TicketEvent]) => Future[WriteEventsCompleted] = Eventstore.write[TicketEvent]): TicketEither[TicketModel.State] = { | |
| val streamId = ticketEventStreamId(command) | |
| eventReader(streamId).map { events => applyTo(TicketModel.EmptyTicket: TicketModel.State)(events) } | |
| .map { state => (state, decide(state, command)) } | |
| .flatMap { case (state, decideResult) => | |
| decideResult.traverse { events => | |
| eventWriter(streamId, events).map { _ => applyTo(state)(events) } | |
| } | |
| } |> fEitherT | |
| } | |
| private def ticketEventStreamId(command: TicketCommand): EventStream.Id = EventStream.Id(s"ticket-${command.aggregateUid.safeValue}") | |
| } | |
| object TicketCommandHandler extends TicketCommandHandler |
| object TicketModel { | |
| sealed trait State | |
| case object EmptyTicket extends State | |
| case class Ticket(uid: SafeTicketUid, | |
| agencyUid: SafeAgencyUid, | |
| user: TicketUser, | |
| operatorUid: SafeOperatorUid, | |
| infos: TicketInfos, | |
| openedDate: DateTime, | |
| journal: List[OtherTicketEvent], | |
| isClosed: Boolean = false) extends State | |
| } |
| trait GenericTrait { | |
| def generic[A: TypeClass](x: TypeClass[A]#Result)(y: List[A]): TypeClass[A]#Result = | |
| y.foldLeft(x) { (a, b) => TypeClass[A].aMethod(a, b) } | |
| } | |
| import scala.annotation.implicitNotFound | |
| @implicitNotFound("No member of type class TypeClass found for type ${A}") | |
| trait TypeClass[A] { | |
| type Result | |
| def aMethod(x: Result, y: A): Result | |
| } | |
| object TypeClass { | |
| def apply[T: TypeClass]: TypeClass[T] = implicitly[TypeClass[T]] | |
| implicit object InstanceTypeclass extends TypeClass[AnInstance] { | |
| type Result = AResult | |
| override def aMethod(x: Result, y: AnInstance): Result = ??? | |
| } | |
| } | |
| case class AnInstance() | |
| case class AResult() | |
| /** | |
| * [error] /../TypeClass.scala:3: type mismatch; | |
| [error] found : a.type (with underlying type domain.service.TypeClass[A]#Result) | |
| [error] required: _5.Result where val _5: domain.service.TypeClass[A] | |
| [error] y.foldLeft(x) { (a, b) => TypeClass[A].aMethod(a, b) } | |
| [error] ^ | |
| [error] one error found | |
| [error] (compile:compile) Compilation failed | |
| * / |
I would also advise using an Aux type to force scalac to resolve all types as following:
trait GenericTrait {
def generic[A, R](x: R)(y: List[A])(implicit tc: TypeClass.Aux[A, R]): R =
y.foldLeft(x) { (a, b) => tc.aMethod(a, b) }
}
import scala.annotation.implicitNotFound
@implicitNotFound("No member of type class TypeClass found for type ${A}")
trait TypeClass[A] {
type Result
def aMethod(x: Result, y: A): Result
}
object TypeClass {
type Aux[A, R] = TypeClass[A] { type Result = R }
implicit object InstanceTypeclass extends TypeClass[AnInstance] {
type Result = AResult
override def aMethod(x: Result, y: AnInstance): Result = ???
}
}
case class AnInstance()
case class AResult()
Thx both of you for your answer.
2 points :
- I don't understand what do
TypeClass.Aux - Because I use dependant type, I can't use this notation
def generic[A: TypeClass]instead of implicit notation ?
-
TypeClass.Auxreifies the typeResultfor scalac because you will soon encounter cases where scalac won't be able to unifyResultbetween 2 instances ofTypeClass... scalac has limitations clearly in this domain so you have to help it by forcing it to identify the type dependent... Shapeless is all based on this idea... -
if you use the type
Aux, no you won't be able to use this notation and actually I don't use it much becauseimplicitlyis ugly when you have to use it several times in the same function
I have to leave, i'll answer later...
that's the last part of the question... your code compiles but how do you use it... your type-dependent is a bit weird and you need to show some usage code to see what is the right way to write it ;)
No it's ok this compile. But I lost some message i post in gist.
Thx for your help.
Should events: List[E] be an HList instead ?
TypeClass[A]#Resultis a type projection whereas thex: ResultinaMethodis a dependent type. The latter is much more specific and constrained. You could makegenericcompile by relaxing the type as follows:But that's hardly what you want because your
aMethodcannot do anything useful withx. Instead you probably want to changegenericto use the dependent type: