Fun with object algebras and partial type application

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?

Doh - WrappedResult[CoreResult, _] is an existential type, not a partially applied type. Got it.

(Explanation)