So i did quick experimentation with elementwise operator improvements: (1) stuff like 1 + exp (M):
(1a): this requires generalization in optimizer for elementwise unary operators. I've added things like notion if operators require non-zero iteration only or not. (1b): added fusion of elemntwise operators, i.e. ew(1+, ew(exp, A)) is rewritten as ew (1+exp, A) for performance reasons. It still uses an application of a fold over functional monoid, but i think it should be fairly ok performance/DSL trade-off here. to get it even better, we may add functional assignment syntax to distributed operands similar to in-memory types as descrbed further down. (1c): notion that self elementwise things such as expr1 * expr1 (which is surprisingly often ocurrence, e..g in Torgerson MDS) are rewritten as ew(A, square) etc. So that much works. (Note that this also obsoletes dedicated scalar/matrix elementwise operators that there currently are). Good. The problem here is that (of course!) semantics of the scala language has problem importing something like exp(Double):Double alongside with exp(DRM):DRM apparently because it doesn't adhere to overloading rules (different results) so in practice even though it is allowed, one import overshadows the other. Which means, for the sake of DSL we can't have exp(matrix), we have to name it something else. Unless you see a better solution. So ... elementwise naming options: Matrix: mexp(m), msqrt(m). msignum(m) Vector: vexp(v), vsqrt(v)... DRM: dexp(drm), dsqrt(drm) .... ? Let me know what you think. (2) Another problem is that actually doing something like 1+exp(m) on Matrix or Vector types is pretty impractical since, unlike in R (that can count number of bound variables to an object) the semantics requires creating a clone of m for something like exp(m) to guarantee no side effects on m itself. That is, expression 1 + exp(m) for Matrix or vector types causes 2 clone-copies of original argument. actually that's why i use in-place syntax for in-memory types quite often, something like 1+=: (x *= x) instead of more naturally looking 1+ x * x. But unlike with simple elementwise operators (+=), there's no in-place modification syntax for a function. We could put an additional parameter, something like mexp(m, inPlace=true) but i don't like it too much. What i like much more is functional assignment (we already have assignment to a function (row, col, x) => Double but we can add elementwise function assignment) so that it really looks like m := exp That is pretty cool. Except there's a problem of optimality of assignment. There are functions here (e.g. abs, sqrt) that don't require full iteration but rather non-zero iteration only. by default notation m := func implies dense iteration. So what i suggest here is add a new syntax to do sparse iteration functional assignments: m ::= abs I actually like it (a lot) because it short and because it allows for more complex formulas in the same traversal, e.g. proverbial R's exp(m)+1 in-place will look m := (1 + exp(_)) So not terrible. What it lacks though is automatic determination of composite function need to apply to all vs. non-zeros only for in-memory types (for distributed types optimizer tracks this automatically). i.e. m := abs is not optimal (because abs doesn't affect 0s) and m ::= (abs(_) + 1) is probably also not what one wants (when we have composition of dense and sparse affecting functions, result is dense affecting function). So here thoughts are also welcome. (I am inclining to go with ::= and := operators because functional assignment of complex expressions is the only well performing strategy i see here for in-memory types).