-
Notifications
You must be signed in to change notification settings - Fork 0
Algebraic Effects for Context
These are some notes towards using Algebraic Effects as a design paradigm for the TDA4j algebra interfaces.
- Direct-style Effects Explained
- Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
The effect choice we will work with is the choice of field for the computation. Picking a different field changes the results of a computation with chains and cellular complexes -- but does not necessarily change the computation itself (especially for the simplicial boundary operator, which is fully describeable using only the Fractional[T]
interface without any numeric primitive values.
Building off of the Direct-style Effects Explained blog post, here is one example of how this design could work out:
type FractionalComputable[A] = CoefficientIsFractional ?=> A
trait CoefficientIsFractional {
type CoefficientT
val coefficientIsFractional : Fractional[CoefficientT]
given Fractional[CoefficientT] = coefficientIsFractional
def apply[A](body : FractionalComputable[A]): A =
body(using this)
}
This one is designed to be used to prefix-tag executable blocks, see below for an example.
object FrComp {
inline def apply[A](inline body : CoefficientIsFractional ?=> A): FractionalComputable[A] = body
}
object DoubleIsFractional extends CoefficientIsFractional {
override type CoefficientT = Double
override val coefficientIsFractional: Fractional[Double] = math.Numeric.DoubleIsFractional
}
object Fp3IsFractional extends CoefficientIsFractional {
val fp3 = FiniteField(3)
override type CoefficientT = fp3.Fp
override val coefficientIsFractional: Fractional[CoefficientT] =
fp3.FpIsFractional
}
I had to make this one extra lazy to get the Ammonite console to play nicely. Either val
or lazy val
would make the console try to print it out, which made it try to evaluate it, which is not possible because there's no available CoefficientIsFractional
object around.
Ideally, these should be possible to state as val
instead when we need them, just need to figure out how to make that workable.
import scala.math.Fractional.Implicits.infixFractionalOps
def frcomp : FractionalComputable[Boolean] =
FrComp {
val cfr : CoefficientIsFractional = summon[CoefficientIsFractional]
import cfr.{*,given}
val fr : Fractional[CoefficientT] = summon[Fractional[CoefficientT]]
val x : CoefficientT = fr.one + fr.one + fr.one
x == fr.zero
}
So, here, frcomp
will compute 1+1+1
in whatever field we decide to work in, and output a boolean dependent on whether or not the result is zero in that field. Ideally I would like to find a way to reduce the import
s and summon
boiler plate at the beginning, but it is not yet clear to me how.
Once we have our computation specified, we can run the actual computation by feeding in an object carrying the field definitions:
@ DoubleIsFractional(frcomp)
res10: Boolean = false
@ Fp3IsFractional(frcomp)
res11: Boolean = true