Interesting! Clearly my knowledge of the clojure primitives was extremely vague; I wasn't even aware that they operated on forms rather than function values.
(-> "a b c d" upcase [replace _ "A" "X"] [split _ " "] first)
What's more, you could mix first and last because all the arguments are actual values rather than hacky s-exprs. I already used that in the second example above, if you look closely.
---
The one missing use case is this:
(-> animals 'dog 'thaddeus 'age)
Arc just has a different, retro-email solution for that:
animals!dog!thaddeus!age
Though it all unravels if one of the keys is a string. Ok, we could use some help here. But it feels like a separate mechanism; I would avoid mixing `(,form ,x) and `(,x ,form) in different paths of a single macro.
---
Hmm, even this isn't too bad:
(-> animals [_ 'dog] [_ 'thaddeus] [_ 'age])
You don't save typing compared to:
(((animals 'dog) 'thaddeus) 'age)
But the matching parens are closer together and so easier to read.
Yes, you'll get broken pipe errors if the browser closes the connection and stops reading prior to completion. I'm guessing some of your users are probably reloading/leaving due to these long response times.
The Clojure operator '->', as I know it, is called 'thread-first'. And in Clojure it used to inject a value (as the first argument) into a provided expression. It then returns a result which will get piped through, in the same manner, to any additional expressions supplied.
There is also a thread-last '->>' which obviously injects the value as the last argument. It looks like you've decided that thread-last is more useful in Arc (which I would agree with) and have chosen the '->' operator.
I think that's a great idea.
For interest sake here are my conversions of thread-first and thread-last respecting Clojures implementation:
In Clojure, however, the thread-first operator works really well when querying deeply nested tables/hash-maps while in Arc these do not. This really is just because of the difference in hash-map/table implementations.
arc> (= animals (obj cat (obj toby (obj age 9))
dog (obj thaddeus (obj age 5))))
...
arc> (-> animals 'dog 'thaddeus 'age)
5
I believe this will work for your afn/recstring example, but the other one would be a little different:
arc> (-> [* _ 2] 3)
6
[note: I had to install arc to give this a try - not having used it for probably a year or so. So if it's really hacky my apologies, but took me 10 mins just to re-learn how to write even really basic expressions, and I certainly don't remember being very good with macros.]
This is interesting, but my first reaction is that push wouldn't necessarily need to be a macro at all with the right reference-passing/modifying semantics. For instance, Arc's scar & scdr manipulate the contents at an address:
Coming up with a simultaneously clean, terse, and simple pointer model is a different exercise, I suppose. And not one I'm particularly qualified to participate in (I'm no C guru).
Have you found any other use-cases for this qcase idea?
It's not often the case, but when eval is right, it's right.
Good work! Looks like you have all the angles covered in your point-by-point breakdown at the end, and evaluating is really all the CL version is doing too. Honestly, the biggest "gotcha" for me is point 2, but you neatly deal with that. The tradeoff is totally worth it for having on-line "redefinition" of the macros you're testing. Just make sure 2 is documented as a caveat, and you're good to go.
Certainly a shorter answer to the whole discussion than the naysaying I launched into. ;)
Very interesting! Particularly the <- and -> operators. It's refreshing to see new approaches to stack shuffling that don't come from the kind of established zeitgeist of stack shufflers (dup, swap) & combinators (dip, bi, tri). I was confused by the notions of the "downstack" and "upstack", because it looks to me that you really only have one stack, and a cursor into it, like:
-- (bottom) n
-- ^
<- -- (bottom) n
-- ^^^^^^^^
{ '*' << } -- (bottom) { '*" << } n
-- ^^^^^^^^^^
-> -- (bottom) { '*' << } n
-- ^
times
or
-- (bottom) a b c
-- ^
<- -- (bottom) a b c
-- ^
<- -- (bottom) a b c
-- ^
operate_on_a -- (bottom) whatever operate_on_a produces b c
-- ^^^^^^^^
-> -- (bottom) whatever operate_on_a produces b c
-- ^
etc.
Also, it seems to me the language isn't really "typeless"; rather, the programs aren't generally type-directed. The system appears similar to Arc's, where you have a few already-used type tags (string, int, num), but in principle the tags are arbitrary. I suppose one distinction could be the treatment of the data under certain operations: Bipedal has syntax for strings and integers, but what happens when you call + on a string and an integer? Is it just treated like adding the string's address with the integer, perhaps?
Finally, I'm interested by the crypto angle. Do you think this is just a generally important (or interesting) feature, or is there a particular application you have in mind? [I said, hoping you're still checking this forum. :)]
Thanks. On a related note, I've been having fun populating the Rosetta Code page for wart: http://rosettacode.org/wiki/Category:Wart. After seeing this I just realized that arc has no representation at all!
I had imagined that the table would just be defined in advance from a src file. That would be consistent with other comparable features arc offers. And the memoization would just be a code efficiency thing.
If we don't care about being consistent, it certainly would be cool to have the table load from a data file, and then to have the first run of an override write to file when it's not present. Memoization to, again, cover code efficiency.
As for use cases I only had a few plural overrides I needed to deal with and I too just hard coded them. At the time I did so I remember thinking I should put it in a table, but never got around to it. Then I saw your post and I thought of it again!
Note that I don't really use arc these days, but I have ported most arc functions over to clojure and I'm always looking to improve my library. In my clojure world I would just store them in datomic as facts. I think I might run along and do this now that I've spent some time thinking about it ... ;)
A lookup table would be interesting. I considered that, but wavered -- would it be filled at write-time, or by some sort of memoization? Keep in mind that if it's memoized, you have to worry about when you tell the function what the plural should be, or you could get inconsistent data.
Is there a use case where you're not aware of the exception when calling the function?
It seems to me , however, that a better solution would be to store the exceptions in a table, then for plural to check for an exception value before defaulting to adding an s.
As you have defined it, we would need to be aware of each exception when the function gets called, often having to add hard coded data for each exception condition and possibly many times.
[edit: what would be really nice is to keep your code along with using a lookup table + memoization.]
Shouldn't Arc provide something like pthread_join? A function that wakes up the thread that waits -- rather than continually checking for all threads to exit?