arclanguage / anarki

Community-managed fork of the Arc dialect of Lisp; for commit privileges submit a pull request.
http://arclanguage.github.io
Other
1.17k stars 160 forks source link

Nuke OUT; Update EACH to `(accum out ...)` #152

Closed shawwn closed 5 years ago

shawwn commented 5 years ago
arc> (each x '(a b c))
'nil

arc> (each x '(a b c) (out x))
'(a b c)

arc> (each x '(a b c) (out (list x 1)))
'((a 1) (b 1) (c 1))

arc> (each (k v) (obj a 1 b 2) (out (list k v)))
'((b 2) (a 1))

arc> (listtab (each (k v) (obj a 1 b 2) (out (list k v))))
'#hash((b . 2) (a . 1))
shawwn commented 5 years ago

I also had an idea related to accum. I've updated it to work like this:

arc> (each x (obj a 1 b 2) (out x))
'((b 2) (a 1))

arc> (each (k v) (obj a 1 b 2) (out k v))
'((b 2) (a 1))

And calling the accum function with 0 arguments will return all the currently-accumulated values (and reset the accumulator).

This seems like what you'd always want, as a user.

shawwn commented 5 years ago

To clarify what I mean:

arc> (accum a (a 1) (a 1 2) (a 1 2 3))
'(1 (1 2) (1 2 3))

arc> (accum a (a 1) (a 1 2) (a 1 2 3) (prn (a)) (a 'reset))
(1 (1 2) (1 2 3))
'(reset)
akkartik commented 5 years ago

Seems interesting, and I see it adds functionality without really breaking anything. (I don't think people rely on the result of each.) Ship it!

rocketnia commented 5 years ago

When I saw you write (out (list x y)), it crossed my mind that it's a good thing you didn't do that with (out x y), since that would mean (apply out xs) should accumulate the list xs, and therefore (out x) should accumulate a list containing x.

I thought about it for a second and settled on the idea that if (apply out xs) meant anything, it would mean "accumulate each of the elements of the list xs in some order".

Then I kept reading and you did use (out x y) as a shorthand for (out:list x y) after all. Couldn't these different behaviors for zero arguments, one argument, and two arguments have used different names? And isn't the name out:list pretty concise already?

And calling the accum function with 0 arguments will return all the currently-accumulated values (and reset the accumulator).

This seems like what you'd always want, as a user.

That makes the state non-monotonic, which is pretty surprising to me since I associate accum with append-only list generation. Programming in Arc and finding out that so much of my code could use an append-only style and become simpler as a result was very satisfying.

When I was excited that adding accum functionality to each could make each more compositional, it was mostly because I was pondering how this would be extrapolated to channels or coroutines. The operation "unsend all the messages that have been sent so far" is clearly not a very viable operation for channels or coroutines, so, so much for that. XD

The operation you describe is familiar to me, though. In some of my side-effectful JavaScript code, I've sometimes written operations that wipe out the current accumulation state and add that list of values to another list. I call them bank, like what they shout on The Weakest Link. :-p This usually happens when I'm looping over one list and building a list of lists out of it.

Incidentally, I think each would be a better design if out wasn't an anaphoric variable. You said writing (each a x xs ...) would be ugly, but I disagree, especially if the new functionality uses a new name like (accum-each a x xs ...). I think it'll be common to want to use out from inside multiple nested loops (since that becomes monadic composition of the lists looped over this way), but that's cumbersome to do when each loop shadows the binding of out. Writing (let acc out ...) to avoid unwanted shadowing is at least as cumbersome as writing (accum acc ...) in the first place.