This came up recently for me, I’ve made a toy example below.
Context:
I’ve got various object algebras that some system drives; depending on the context the object algebras deal with a Result
that may be used for production or testing in different flavours of result. So here Vector
is an example result used as a log.
There is a specific kind of result, WrappedResult
that delegates to a simpler result; it adds some extra functionality. It is tested by wrapping it over an auditing result; expectations are applied to the auditing log. There are several different operations in the ‘real’ algebra, and it is important to show how the pass-through works, it is not always one-to-one for each operation.
(The reason for the auditing log is to finesse my current approach, namely using Mockito to supply the core result as a mock and using imperative verification to prove correctness of pure functional code. It works for me, but I feel bad about it, and it may come back to bite me later as things get more complex).
trait ObjectAlgebra[Result[_], Datum]:
def emptyResult: Result[Datum]
def add(partialResult: Result[Datum], datum: Datum): Result[Datum]
end ObjectAlgebra
class BigComplexAbstractingSystem[Result[_], Datum](
algebra: ObjectAlgebra[Result, Datum]
):
def calculationsGalore(data: Seq[Datum]): Result[Datum] =
// OK, just push data through the algebra to grow a result, it's a toy
// example here...
data.foldLeft(algebra.emptyResult)(algebra.add)
end BigComplexAbstractingSystem
val loggingAlgebra = new ObjectAlgebra[Vector, String]:
override def emptyResult: Vector[String] = Vector.empty
override def add(
partialResult: Vector[String],
datum: String
): Vector[String] = partialResult :+ datum
case class WrappedResult[Result[_], Datum](
coreResult: Result[Datum],
extraStuff: String
)
def wrappingAlgebraTakeOne[CoreResult[_], Datum](coreAlgebra: ObjectAlgebra[CoreResult, Datum])
// Type argument WrappedResult[CoreResult, ?] does not have the same kind as its bound [_$1]
: ObjectAlgebra[WrappedResult[CoreResult, _], Datum] = ???
type Fudge[CoreResult[_]] = [Datum] =>> WrappedResult[CoreResult, Datum]
def wrappingAlgebraTakeTwo[CoreResult[_], Datum](coreAlgebra: ObjectAlgebra[CoreResult, Datum])
: ObjectAlgebra[Fudge[CoreResult], Datum] = ???
So the naive approach doesn’t compile, but after thinking of type lambdas and bashing my head against the keyboard a few times, I got Fudge
and it compiles.
Great!
My question is, is there a more finessed way of defining the wrapping algebra? What was wrong with the first approach using partial type application that Scala 3 didn’t like?