this is an thread building on this other thread: #ideas > Numeric infix ops with the goal of discussing ideas for how to try to get scalars to work in matrix arithmetic like in this example:
Lakin Wecker said:
However, let's take your example slightly further with something like a fourth order integration Runge Kutta and show the differences between your method based syntax and something like C++.
Equation:
My imagined Roc syntax:
y_n1 = Vec3d.add y_n (Vec3d.mul (Vec3d.div h 6) (Vec3d.add (Vec3d.add (Vec3d.add k_1 k_2) (Vec3d.mul 2.0 k_3)) k_4))What I want:
y_n1 = y_n + (h / 6.) * (k_1 + (2. * k_2) + (2. * k_3) + k_4)I can super easily verify the correctness of the last one. But the syntax for Roc is quite difficult for me verify. In fact, there is an omission in there, which was not intentional, and I didn't notice until I was typing out the syntax I want.
here's a concrete idea for how this could work
suppose we take the matrix-specific abilities discussed #ideas > Numeric infix ops and have all of those abilities require this function:
fromNumLiteral : Num * -> a where a implements ThisMatrixAbility
now any custom userspace matrix type which supports infix operators also includes a way to convert a number literal (note that it's only number literals - so, Num * specifically) into itself
at that point, if we add some basic dynamic Matrix type to builtins (which, separately, would remove the somewhat weird situation of having matrix abilities in the builtins but no matrix type that they ever get used for - while still letting others build their own custom matrix types for their own performance needs), then I believe we could change the unification rules for Num * to unify with matrix types to become that matrix type, by automatically converting the scalar into a matrix
(I'm not 100% certain this is possible, but my intuition is that it's possible)
at that point, the one missing piece would be that we'd need to handle the case where you do something like 2 ** 3 in the repl (so, matrix multiplication of two scalar literals - which I think would type-check in this design, unless maybe there was some way to rule that out) - which is where the default matrix implementation in builtins could come in
so similarly to how if you do 2 * 3 we have multiplication of two Num * values, so we pick a particular integer size and use that...we could have 2 ** 3 pick the one builtin matrix type and use that
assuming all that works, I think in practice having myMatrix * 2 "just work" could have a similar feel to how myF64 * 2 "just works" even though in some languages you'd have to do my64 * 2.0
as a potential bonus, if we had some sort of matrix ability for arithmetic on scalars (e.g. an ability member like matMulScalarLiteral : a, Num * -> a where a implements ThisMatrixAbility), then it seems like it would be pretty fast during code gen to see if either of the arguments to a multiplication operation was a call to fromNumLiteral, and if so, swap the multiplication operation out for a call to matMulScalarLiteral
if it worked, that could prevent the "you don't actually want to expand the entire scalar in place to a giant matrix when all you're going to do with that is multiply it by the matrix you actually care about" problem, although it would be a bit awkward that one userspace function call would optimize into a completely different userspace function call, so maybe there's some design that removed the fromNumLiteral intermediary :sweat_smile:
With dynamic matrices, the scalar should not be a problem, just make it a matrix with shape [1]. For static, it would proabbly mean filling a full matrix with the scalar value.
right, I guess the latter case would be unfortunate but the former would be fine
Brendan Hansknecht said:
With dynamic matrices, the scalar should not be a problem, just make it a matrix with shape
[1]. For static, it would proabbly mean filling a full matrix with the scalar value.
If you have multiple internal representations of a matrix, including sparse matrices, you can probably get a reasonably efficient representation of a constant matrix. So it might suffice to lean on that and various matrix contructors. Mat4d.constant 4 for example.
For sure, if you matrix is a tag union anyway, it would have a constant form (though that is an extra branch for all ops of a small static tensor which wouldn't be great)
It is only an issue if you have a simple matrix representation (which any builtin form probably would be). This is also why I am pushing for enabling in userland
Last updated: Jun 16 2026 at 16:19 UTC