Proposal: implement "operators" on tuples #1227
Replies: 5 comments 25 replies
-
There absolutely is, as janet has closures. (defn myplus [& xs]
(if (all indexed? xs)
(print "hello from myplus")
(+ ;xs)))
(def + myplus)
(print (+ 2 3))
(+ [1] [2]) You can capture + only to later redefine it. |
Beta Was this translation helpful? Give feedback.
-
I can see the value of defining custom operator behaviors on a per-project basis. However, my instinct would be that defining language-wide defaults for things like I obviously don't have hard data to back this up, but I would expect that the majority of the time, in the majority of simple programs, passing a data structure directly to If that is the case, then most of the time having But we don't want to increase bug tolerance in the language—whenever possible, we want obvious bugs to cause errors, and cause them as close to the origin as possible, so the programmer is alerted to them early and can go understand and then fix them. Having a way on a per-project basis to define custom operator behavior seems reasonable, and also like an effortful way of expressing "I know what I'm doing, so SUDO let me add tuples together." But as I stand right now, I would oppose defining any particular project-convenient behavior at the language level. |
Beta Was this translation helpful? Give feedback.
-
I like the idea, in principle. Adding two vectors adds the vectors, multiplying a vector by a scalar scales the vector. It makes sense, from a mathematical perspective. However, this behavior can already be achieved with the current syntax, relatively concisely: (map + [1 2 3] [4 5 6])
# ->
@[5 7 9] The second use case is not so concise: (map (partial * 10) [1 2 3])
# ->
@[10 20 30] which I concede is somewhat agonizing to look at. I think there's a few alternative options:
|
Beta Was this translation helpful? Give feedback.
-
You might find it useful to look at a library I’ve written that embeds the J language into Janet:
https://jnj.li/
J is an array language, and thus is particularly suited to the kind of array operations being discussed here, that is: operations which are a) declarative over any dimensionality of array ; b) more efficient than vanilla Janet when it comes to nested data structures.
…
On Aug 5, 2023 at 1:57 PM, <primo-ppcg ***@***.***)> wrote:
This is a somewhat inefficient but correct implmentation of vec:
(defn vec [op & inds] (def iters (map indexed? inds)) (if (any? iters) (do (def res @[]) (def ninds (length inds)) (def iter-keys (array/new-filled ninds)) (def call-buffer (array/new ninds)) (while (any? iters) (forv i 0 ninds (def ind (in inds i)) (if (indexed? ind) (if (in iters i) (if-let [key (next ind (in iter-keys i))] (array/push call-buffer (in ind (set (iter-keys i) key))) (put iters i false))) (array/push call-buffer ind))) (if (any? iters) (array/push res (vec op ;call-buffer))) (array/clear call-buffer)) res) (op ;inds)))
Usage:
(vec + [[1 2] 3] 4 [5 [6 7 8] 9]) # -> @***@***.*** 11] @[13 14 15] 13]
—
Reply to this email directly, view it on GitHub (#1227 (reply in thread)), or unsubscribe (https://github.com/notifications/unsubscribe-auth/AAAXT7NKIIZ7WUMQ34F5ALDXT2CPNANCNFSM6AAAAAA2PMPHFY).
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
I wonder what the opinion is on moving this to a separate C library that implements vector operations. Here is a rough API surface:
So some examples: (v+n [1 2 3] 1)
(v+= pos (v* direction speed))
(v+= (enemy :pos) (v* (enemy :dir) (enemy :speed) time-elapsed))
(defn get-mouse-local [camera]
(v- (v/ mouse-position frame-scale)
(v* frame-size 0.5))) Several whys:
My main question would be, what is the necessity to include it into core that can not be implemented outside? Convenience is one thing, is there anything else? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
The Janet "operators" -- the functions that can be implemented as methods, like
+
andbxor
and friends -- are not defined on tuples or arrays. And (as far as I know), there is no way for a Janet user to implement them on tuples or arrays.I would like to propose that Janet implement them with "vector space" semantics, such that adding two tuples zips them together:
While adding an indexed and a non-indexed value applies the operation elementwise:
(And so on for
*
and/
and friends.)Upsides
A lot of what I do with Janet involves vectors, so I'm biased here, but this would be very convenient for working with Raylib or, theoretically, art playgrounds.
I have always overridden the default functions with custom versions that have these semantics, but there's some overhead to doing the extra typecheck that can add up in realtime animation wasm stuff.
I think that this is just nice behavior, but I usually work in a particular niche, so I wanted to see if this idea was compelling to a wider audience.
Downsides
At the moment these expressions can only error:
But this expression in general might not:
If
x
implements a custom:+
. Which is fine -- that will continue to work exactly as it does before. But this will change:Expressions that rely on
:+
not being implemented for tuples will change. Previously this would take the custom:r+
:But after this proposal, it will use the normal
+
defined on the tuple, and invoke:r+
for each element:This feels very low risk to me because I don't think it's very common to overload operators.
Questions
What if the operands are different lengths?
There are, I think, four reasonable options:
Error:
Take the longest common prefix:
Pad the shorter inputs with the identity element:
(i.e., the same as
(+ [1 2 0] [4 5 6])
.)Always return a result with the same length as the first indexed argument, choosing between (2) and (3):
I think that any of these are fine, but I would vote for (3). If you think of "adding" a two-dimensional vector and a three-dimensional vector, I think it makes the most sense to return a three-dimensional vector. And if you have, say, an
[R G B A]
vector, sometimes you want to manipulate the color components without touching the alpha channel.Also maybe worth saying, but (3) is tractable because I'm not suggesting a general tuple-function-zipping thing, but only to define this for
+
and*
and the like, where we can always pick the correct identity element.That said, it's easy enough to pad a vector out --
(+ rgba [;rgb 0])
-- so none of the choices make a big difference.Variadic?
Yeah of course:
What if the operands are different types?
I think the only reasonable answer is to return the first tuple type encountered. So:
What about nested tuples?
I don't think there should be any special-casing. The rules of "adding exactly two indexed structures together zips, everything else maps" still applies correctly with nested tuples:
What about other operations?
I think it's probably a bad idea to apply this to any function that is not already overloadable in the way that the operators are. i.e. I am not proposing that
(math/pow [1 2 3] 2)
should square each element of the "vector." I mean, it might be neat, but that's a separate proposal.Do all abstract types / tables that override operators have to think about this?
Yes, if they want to apply that operator elementwise when they get a tuple as the second argument.
Wait so should abstract types have some way to opt into "I am indexable" so that if you add a tuple with your logically indexed abstract type it doesn't resort to elementwise addition?
That is to say:
Would result in:
When you could reasonably wish for it to be:
But this does not seem worth the complexity to support.
Beta Was this translation helpful? Give feedback.
All reactions