> Attributes are specified by symbols, so there's no ambiguity.
I agree. This is one thing I really dislike in arc. There's ambiguity everywhere, not just in the html libraries.
For example the obj macro:
(obj first "Farley" last "Mowat")
The case macro:
(let x 'y
(case x y (pr "YES")))
It's just everywhere. And it particularly annoys me because it means you can not pass in functions as arguments to be evaluated (for the symbol elements).
Unfortunately your library, even though an improvement, would be one more layer of inconsistency. And by that I mean it becomes yet another mind switch I need to make when using arc + library-x. It's like the entire language needs an overhaul to remove the ambiguity, making it easier to adopt libraries such as yours.
Also, as a question:
(e "div" 'class "myclass" "content" "content")
(e 'div 'class "myclass" "content" "content")
? After all "div" would generate a non-string output.
I can certainly understand if you'd prefer for certain Arc utilities to not be automatically quoted for you, since it saves a single character and costs a lot of flexibility, but I wouldn't say it's ambiguous.... I think I'd use "ambiguous" to refer to code that takes advantage of unspecified behavior.
I'm sure you've considered this, but we can technically accomplish the kind of code you prefer:
(copy (table) 'first "Farley" 'last "Mowat")
(tablist:pair:list 'first "Farley" 'last "Mowat")
; using 'switch from Anarki's lib/util.arc (but it uses (is a b)
; rather than testify.a.b, so functions still don't work)
(let x 'y
(switch x 'y (pr "YES")))
But I agree, it'd be nicer if the standard utilities had these behaviors, so that good behaviors could have good names. ^_^ (Incidentally, this is a place where namespaces could make the issue a matter of library development rather than language overhauling.)
As for your question about (e "div" ...) versus (e 'div ...), I think it would work either way. The tag name (not to be confused with an attribute name) can be anything that has the desired [pr _] behavior.
Unless you've memorized every macro's inner workings it's clearly open to more than one interpretation.
That's the thing. To interpret (case ...) at all, you have to know what 'case is supposed to do as a macro. At least one suggestion on this forum (http://arclanguage.org/item?id=10858) would intentionally(?) have your example work in a different way:
(case x 'y (dosomething))
== reads as ==>
(case x (quote y) (dosomething))
== expands to ==>
(let gs123 x (if (in gs123 'quote 'y) (dosomething)))
When I started out with lisps, I had trouble with this. I kept reading (quote x) as though it might evaluate x, or (let ((foo bar)) ...) as though it might involve a function call to foo. It wasn't just a matter of vocabulary; even if I figured out what (quote ...) did, some macro form around that code might give it a completely different meaning! What if one of these unintelligible commands had the side effect of rebinding 'quote? It took me a while to realize lisp programmers weren't quite as sadistic as that, but that revelation in itself doesn't make the whole issue go away. ^_^ You sorta do have to memorize every macro.
Of course, we could just go for read macros everywhere, rather than putting cheap abbreviations into the normal macros:
; assuming 'x abbreviates (quote x), which it does
(obj 'key "value")
; assuming #'x abbreviates (fn () x)
; assuming #,x abbreviates (fn (gs123) (zap gs123 x))
(push elem #,place)
; assuming #. var x y z abbreviates (fn (var) x y z), with the read
; macro terminating once a mismatched closing parenthesis is reached
(w/outstring #. var
; assuming all of the above
(mac #,case (var . body)
(w/uniq #. g-var
`(let var #. ,g-var (case-part-2 ,g-var ,@body))))
(case x #''y #'(dosomething))
A good portion of the macros I write do nothing but prevent the need for these abbreviations (except they do put the #. variable in a more idiomatic spot), so it's a significant move. I kinda intended to show how bad it was, but what do you think? If we found hieroglyphics we could get used to, maybe they wouldn't be bad at all....
I don't know if I speak for thaddeus here, but I do think (obj 'a 1 'b 2) would a better API than (obj a 1 b 2), for a couple of reasons. For one thing, since it's something you may want to use a lot, it helps to be able to continue to use it even in the odd cases where you have keys you want to evaluate.
The other reason's a bit more in-depth: I think it's oftentimes good practice to use gensym keys, and especially gensym types. It helps prevent libraries from conflicting with each other. When using this technique, an 'obj that doesn't evaluate its keys is pretty useless, but an 'obj that does evaluate its keys is as convenient as (obj a 1 b 2) again, where 'a and 'b are variables holding the gensyms.
What I really think is best is to just have both. Looking at my site-generator code, I see that I defined (objal a 1 b 2) in terms of (tabal 'a 1 'b 2). :-p I think that's generally why I make so many macros that are merely cheap abbreviations of functions, actually; I like calling the function versions directly every once in a while, but I like saving a bit of line noise in the common cases.
As for your (= 'foo 42) example, I think that's one place where the quote really doesn't fit. No matter how many times you say (= 'foo 42), you should never witness (is 'foo 42), so you're not really assigning the value of that expression. However, if (= #,foo 42) were short for (= [zap _ foo] 42), which behaved as ([zap _ foo] [do 42]), that would totally make its own kind of sense. Of course, I'd want to have it both ways again, so I'd name that version 'fn-= or something so that '= could stay the same.
> What I really think is best is to just have both. ... I think that's generally why I make so many macros that are merely cheap abbreviations of functions, actually; I like calling the function versions directly every once in a while, but I like saving a bit of line noise in the common cases.
I agree with this. Compare it with having naming and anaphoric macros: iflet and aif, rfn and afn, even fn and ; I'd add awhile and whilet to the list even though awhile isn't in official Arc, and fromfile, tofile, and ontofile.
In fact, it is a very general practice in (functional?) programming to first write a function that handles something basic, then write another function on top of it that's more pleasant to work with. It really is ubiquitous. It's good because it separates the primitives from the high-level interfaces; if you want to change the primitives, you don't need to concern yourself with the high-level interface, and if you want to change the high-level interface, you don't need to concern yourself with the primitives. And macros give you flexibility, the ability to construct pleasant interfaces in ways you can't do with pure functions; for example, functions must evaluate all their arguments once, from left to right, while macros don't have to.
In this case, I don't think we have a function that directly does what you're asking for (i.e. it operates like obj except it evaluates the keys). I consider this a problem. Note that in Scheme and CL, we can go (vector x y z) instead of crap like (let u (make-vector 3) (= u.0 x u.1 y u.2 z) u). We should likewise have a good table-constructing function even if it isn't strictly necessary as a primitive. At the moment, this is the closest we have:
(apply listtab (pair:list 'a 1 'b 2))
(obj a 1 b 2)
So I think we should have a function like this:
(def ???? args
(apply listtab pair.args))
What should we call it? I think it's rare enough to want evaluated keys that we don't need to give it a short name like "obj". Maybe "make-table", "make-hash", "make-obj"... Hmm, there's a function named "table". Given no arguments, (table) returns an empty hash-table. We could make this be our table-constructing function--in fact, I think that makes a lot of sense, as the function defined above should return an empty table when given no arguments. I again draw your attention to the analogy of the vector function in Scheme and CL. Only problem is, the table function currently defined in Arc actually is overloaded: if you give it an argument, it will apply this argument (which should be a function) to the table before returning the table.
I grepped Arc for '(table ' and found three instances (all in news.arc) where the function table was actually called with an argument [the other matches were, like, (tag (table width "100%" cellspacing ...))]. They look like this:
[each ip ips
(= (_ ip) (dedup (map !by (+ (bads ip) (goods ip)))))]))
(table [each-loaded-item i
(awhen (and i!dead (check (sitename i!url) banned-sites*))
(push i (_ it)))]))
The raw-Arc way to do this would be
(let u (table)
(each ip ips
(= (u ip) ...))
and I believe Anarki has a w/table macro that would make it look like this:
(each ip ips
(= (u ip) ...)))
Which, I think, is close enough to the original that PG/RTM (whoever wrote the above code) wouldn't be too displeased if we kicked out the old usage of table. Actually, you know, this alternating-pairs proposed usage only applies when the number of arguments is even; it wouldn't even conflict if we wanted to implement both behaviors: 1 arg -> empty table, even args -> alternating pairs. In fact, it would simplify implementation to do alternating pairs until there are less than 2 args left, and then if there's 1 arg, treat it as a function and apply it to the table, while if there are 0 args, just return the table.
So. Options: (1) Have table do alternating key-value stuff; if there's one argument remaining at the end, apply it to the table; then return the table. (2) Have table do alternating key-value stuff; signal an error if there's an odd number of arguments [and Paul Graham can get used to w/table], or (2a) if there's one argument, apply that function to the table, while if there are an odd number ≥3 of arguments, signal an error. (3) Leave table as is, and find a new name for our function.
I think (1) is the best thing to do at the moment, and (2) is probably a better future goal but I would be fine being overruled on that (its single advantage is error-reporting when you accidentally give table an odd number of arguments). I think (2a) is a stupid hack and (3) is bad. As such, here is an implementation of (1). Replace the old '(xdef table ...' definition in ac.scm with the following:
I gave two ways we can already accomplish the behavior somewhat conveniently even if we don't have something sensible like your version of 'table:
(copy (table) 'a 1 'b 2)
(listtab:pair:list 'a 1 'b 2)
(Actually, I used tablist in that post, which was a bug.)
I could have sworn the Anarki version of 'table allowed for (table 'a 1 'b 2) at one point (which inspired me when I made 'objal and 'tabal for my static site generator), but I looked at the Git blame and didn't find any obvious evidence of that....
Oh, nice. I am not familiar with the copy function, but I missed that "listtab:pair:list" idea. Clever. I'm sure you agree, though, that we should have a nice table function, in the same way that we shouldn't have + defined for us but have to define - ourselves:
(def - args
(if (no cdr.args) ;or no:cdr.args with my recent hack
(* -1 car.args)
(+ car.args (* -1 (apply + cdr.args)))))
I honestly wouldn't mind very much if '- were omitted. >.>; Actually, wait. Arc puts every global in the same namespace, so we'd have a few conflicting versions of '- in our respective personal utilities (assuming we don't all just grab the Anarki version), and that would be somewhat horrible.
But yeah, 'table is much better as a function that can actually construct complete tables all at once. ^_^ In this case, it's not a matter of name conflicts between libraries; it's a matter of the most obvious names being taken for the wrong behaviors. Namespaces are still a potential solution, though.
Namespaces solve the problem flawlessly only if you plan for no one to work on or read anyone else's code, ever.
Taking '- as the example: there are a few non-obvious things about it (how it works with more than 2 arguments, or how it works with 1 argument), and people could reasonably end up with different versions of it (I do think the current version is best, but I wouldn't blame anyone for not thinking about the 3-or-more-variable case or for implementing it differently).
This is not a problem if you use '- as a utility in the definition of your library functions and someone else just imports the exported functions of your library and uses them. But if you intend to, say, paste code on the Arc Forum and expect anyone to understand it, or if you want people to be able to download the source code of your library and make improvements to it, then there's a cost to every non-standard definition you use: the reader has to learn it. If the language doesn't provide "map" and "let" and "with" (which are certainly not primitive; in fact, they're defined in arc.arc), then either you don't use those operators (and your code will suffer for it), or you do use them and that's one more thing the reader needs to learn to understand your code. It's not the end of the world, it's not a game-breaker for all projects, but it's one more obstacle to people understanding your code, and if you can get rid of it, then that is all to the good.
This is why getting people to agree on a good common core language is a good thing even if the language supports namespaces.
...getting people to agree on a good common core language is a good thing even if the language supports namespaces.
That's a good point. ^_^ I don't use Anarki- or Lathe-specific stuff in my examples if I can help it. And I recently mused on the trend for a language to incorporate a lot of features in the standard so as to improve the common ground, so I should have seen that point to begin with.
Namespaces solve the problem flawlessly only if you plan for no one to work on or read anyone else's code, ever.
I've sorta been banking on the idea that everyone programs to their own languages of utilities anyway, and that each of those languages can be talked about, accepted as a common topic, promoted by its proponents, and standardized just like language cores can. Certainly CMSes and game engines have those kinds of communities. :)
Multiple independently developed libraries won't always work together well enough to all be clearly canon components of the language's identity, but I think they are more likely to be interoperable than are things designed from the outset to be entirely new languages. So I don't think it's important for the core libraries to be cutting-edge in practice. The core can fade into the background to a degree.
I haven't thought about this stuff very deeply yet, and it's not rooted in a lot of fact, so please feel free to disillusion me. ^^
 I don't mean to suggest there's a well-ordered expected value of interoperability. Whether a library is designed or used in an interoperable or impregnable way is sorta relative and idiosyncratic. One more avenue for interoperability could even be annoying if people come to depend on it in poorly prepared-for ways. ^_^
Regarding (= 'foo 42): I do think it should be possible to perform assignment on symbols that are chosen at runtime. In Common Lisp, this would be (setf (symbol-function 'foo) 42); in Racket, this is (namespace-set-variable-value! 'foo 42). It's possible to do (eval `(= ,x ',desired-value)) [the ' is necessary because the Arc evaluator doesn't like objects in code], but this feels like a hack and there should be a built-in way to do it (I've said this before, by the way).
What should it be named, and if we wish to use the setf pattern, what should we name the corresponding analogue of "symbol-value"? At the moment, (eval x) does the same thing as (symbol-value x) when x is a symbol [bad things might happen if you do this to 'nil, but otherwise, yeah]. Would (= (eval x) ...) be fine? Or should there be a specialized operator named something like "symbol-value"? For purposes of conservatism, giving a meaning to (= (eval x) ...) seems easiest...
I immediately thought you were suggesting a way to set local variables at runtime, but this way I have a better answer for you. ^_^
Lathe already defines (= global.x desired-value) so that it behaves just like (eval `(= ,x (',(fn () desired-value)))). As an everyday expression, global.x behaves like (bound&eval x), which makes (zap _ global.x) a bit more intuitive. This utility works on Arc 3.1, Anarki, Rainbow, and Jarc.
I don't mind if something like this is added to the language core too, but isn't that less axiomatic?
It is certainly less axiomatic--you don't need this to have a working Lisp. However, it's useful (I needed it to do my little module system), it's likely to be very simple to implement as a primitive (given access to Scheme, we can just use 'namespace-set-variable-value!), and its name clearly maps to its function and its setf behavior. (I'd be inclined to use the name "global-value" or "symbol-value", actually.)
Finally, it's easier to see "symbol-value" written in the language description and use it than to figure out how to use the 'eval method properly. Just look at how complex your definition is; even I, who do see why it's necessary to go ',desired-value instead of ,desired-value in my version [if objs in code worked properly, you could use the former version for a while without noticing it was wrong], I don't see why it's necessary to go (',(fn () desired-value)), and I certainly wouldn't think of it myself. (I still don't believe it's actually necessary, but I won't press the point.) Like the case of (++ (car (pop xs))), this is one of the things a core language is good for: the implementor has considered and avoided all the subtle pitfalls for you. (Actually, implementing it as a primitive with the "namespace-..." functions, there aren't any pitfalls to avoid.)
By the way, I don't know if the core of the language is supposed to be all that axiomatic. It should be clean, certainly, but by its nature it should provide more than minimal axioms. In an essay, Paul Graham talks about "the core language—those operators that are neither primitives like car and cdr, nor special-purpose library functions. I mean operators like CL's mapcar, let, remove-if-not, and so on."
At any rate, here's what I think: (a) There will be people who need the equivalents of 'symbol-value and (setf symbol-value)--people who write module libraries or debugging facilities or for whatever reason want to determine names at runtime. (b) This might be enough by itself to warrant placement in the core language, but (c) it is extremely easy, not to mention efficient, to implement these as primitives, while it is subtle, error-prone, and inefficient to implement it with 'eval. Therefore, it should be built into the language. (Technically, if it's a primitive, then it's not part of the core language by the implicit definition from the essay, but whatever you call it, it should be built in.)
I feel better about rambling/ranting when I also produce something tangible, like working code. So, here you go:
That's why it's in Lathe, too. ^_^ Since it's there to implement the module system, it's one of only a couple dozen Lathe utilities that isn't in a module, but I find it useful enough that I like it that way.
I don't see why it's necessary to go (',(fn () desired-value))
If you embed a literal vector in an expression (for instance, a tagged value), Racket will evaluate that expression as a copy of the vector. So (let foo (annotate 'a 'b) (= global!x foo) (is global!x foo)) would return nil, which I find frustrating. ^^ (There might be significant mutations-aren't-reflected-in-both-copies gotchas too, but I'm not sure right now.)
If you embed a procedure instead, I don't know if it copies that too, but it doesn't matter 'cause a copy of the procedure should still close over the same bindings. In practice, (let foo (annotate 'a 'b) (= global!x foo) (is global!x foo)) works, so I'm satisfied.
By the way, I don't know if the core of the language is supposed to be all that axiomatic. It should be clean, certainly, but by its nature it should provide more than minimal axioms.
it is extremely easy, not to mention efficient, to implement these as primitives, while it is subtle, error-prone, and inefficient to implement it with 'eval.
Sure. Implementing 'symbol-value in terms of 'eval is a pretty big abstraction inversion, and it causes lots of unnecessary consing and compilation at runtime. I'm kinda conviced. ^_^
Incidentally, official Arc allows you to say (assign a.b t) and observe (bound 'a.b). Your 'symbol-value makes it possible to get the value of 'a.b again, whereas there's no non-exploit way to do that with 'eval. ^_^ (You can use the (cdr `(0 . ,_a.b)) bug, lol.)
Speaking of 'bound... why is 'bound a primitive when 'symbol-value isn't? XD Actually, I've wondered why 'bound was a primitive for a while, but that kinda-convinces me further. ^_^
keys are not variable names, so I'll ask (in general) why should obj be treated so special or differently?
Again, as you already pointed out, it boils down to flexibility and consistency. I don't see why I would trade saving one character spot over both 1. being clear & consistent and 2. allowing functions to be passed in:
Right, but once again, adding a few characters may make things more consistent, but does it aid clarity? I really don't know either way.
Using the same heiroglyphics as I posted last time, we can implement 'case entirely as a function:
; Hypothetically, this uses the macros 'def, 'whenlet, 'iflet, and
; 'do; the functions 'if, 'testify, 'get, and 'apply; the special
; forms 'fn, 'zap, and 'quote; and the read macros
; #,x for (fn (gs123) (zap gs123 x)), #'x for (fn () x), and
; 'x for (quote x).
(def #,case (subject . args)
(whenlet (first . rest) args
(iflet (then . rest) rest
; Check the testified result of (first) against the subject.
; Return the result of (then).
#'(apply case-part-2 subject rest))
; Return the result of (first). It's the last else.
; was (case x y (dosomething))
(case x #''y dosomething)
This is even an overall brevity boost, I think. What do you think of its clarity? I seriously might try this out in Penknife....
All the necessary assumptions are listed in the top comment though (assuming you can intuit my intentions for the 'iflet macro, etc.). It's by no means implemented. ^^
Also, I welcome any switch in the heiroglyphics that makes them more readable. In Penknife, where there are infix operators but no read macros, I'd probably represent #''y as "thunk:sym.y" or "sym.y^". In Arc, ^'y looks like a good choice. Maybe :y or %y could be the zapper (or some better-suited abstraction over setforms).