Stream: ideas

Topic: scalars in matrix arithmetic


view this post on Zulip Richard Feldman (Oct 18 2023 at 00:40):

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:

yn+1=yn+h6(k1+2k2+2k3+k4)y_{n+1} = y_n + \frac{h}{6}\left(k_1 + 2k_2 + 2k_3 + k_4 \right)

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.

view this post on Zulip Richard Feldman (Oct 18 2023 at 00:41):

here's a concrete idea for how this could work

view this post on Zulip Richard Feldman (Oct 18 2023 at 00:44):

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

view this post on Zulip Richard Feldman (Oct 18 2023 at 00:45):

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

view this post on Zulip Richard Feldman (Oct 18 2023 at 00:49):

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

view this post on Zulip Richard Feldman (Oct 18 2023 at 00:49):

(I'm not 100% certain this is possible, but my intuition is that it's possible)

view this post on Zulip Richard Feldman (Oct 18 2023 at 00:50):

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

view this post on Zulip Richard Feldman (Oct 18 2023 at 00:51):

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

view this post on Zulip Richard Feldman (Oct 18 2023 at 00:52):

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

view this post on Zulip Richard Feldman (Oct 18 2023 at 00:57):

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

view this post on Zulip Richard Feldman (Oct 18 2023 at 00:59):

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:

view this post on Zulip Brendan Hansknecht (Oct 18 2023 at 02:05):

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.

view this post on Zulip Richard Feldman (Oct 18 2023 at 02:23):

right, I guess the latter case would be unfortunate but the former would be fine

view this post on Zulip Lakin Wecker (Oct 18 2023 at 15:10):

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.

view this post on Zulip Brendan Hansknecht (Oct 18 2023 at 15:11):

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)

view this post on Zulip Brendan Hansknecht (Oct 18 2023 at 15:12):

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