-
-
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.Aux
reifies the typeResult
for scalac because you will soon encounter cases where scalac won't be able to unifyResult
between 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 becauseimplicitly
is 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]#Result
is a type projection whereas thex: Result
inaMethod
is a dependent type. The latter is much more specific and constrained. You could makegeneric
compile by relaxing the type as follows:But that's hardly what you want because your
aMethod
cannot do anything useful withx
. Instead you probably want to changegeneric
to use the dependent type: