Arc Forumnew | comments | leaders | submitlogin
A new html engine, with custom tags and layout templates (github.com)
5 points by hasenj 5103 days ago | 33 comments


4 points by rocketnia 5103 days ago | link

Your library is almost exactly like a hacked-together one I made for my website not too long ago. It's sorta scary, in a good way. ^_^

Mine doesn't parse the argument list manually though. The code instead looks like this:

  (<a (objal href "http://arclanguage.org/") "Arc Forum")
Here, 'objal is a macro that makes association lists tagged with the type 'al. The 'al type is then treated specially by functions like '<a. I think your arglist-parsing is much more convenient, but maybe there could be a hybrid or something. I like having the potential to use utilities other than 'objal for constructing attribute lists.

The other main difference is that my custom type for HTML tags actually has four parts: not only the name, the attribute list, and the body, but also another value to hold explicit rendering hints and overrides. For instance, I can make a tag so that it's rendered as though its content is preformatted (i.e. like the <pre> tag), even if other tags of the same name don't have the same behavior. This would matter more if I actually had a pretty-printer. :-p

Speaking of pretty-printers, one thing overlooked by a lot of HTML generators, including yours, is that whitespace between a tag and its contents can often matter. For instance, this HTML...

  Visit the
  <a href="http://arclanguage.org/">
    Arc Forum
  </a>
  today!
...renders as "Visit the (Arc Forum )today!", where my parentheses indicate the extent of the hyperlink. This is everywhere on the Web, but IMO it's pretty sloppy.

In my manually-written HTML, I've actually opted for this sort of code when I'm desperate to indent things a certain way:

     Visit the
     <a href="http://arclanguage.org/"><!--
    -->Arc Forum<!--
  --></a>
     today!
I've recently been tending toward other, less consistent but more readable techniques, like this one:

     Visit the
     <a href="http://arclanguage.org/"
       >Arc Forum</a>
     today!
(This is also way more convenient when the need arises to comment out a whole section of the page.)

But right now, my generator just puts everything on one line, which also works. :-p

FYI, since your system prints each node on a separate line, it's impossible for a node to not have whitespace around it. For instance:

  (deftag italic
    (tag "i" args))
  
  ; idiomatic, but rendered with incorrect spacing
  (= rocketnia* (list "Rocket-N" (italic "I") "A"))
  
  ; workaround
  (= rocketnia* "Rocket-N<i>I</i>A")
  
  ; incorrect... but no workaround?
  (def manually-quote (html)
    (list "\"" html "\""))
The all-output-on-one-line approach could help here too.

I'm definitely a fan of this 'annotate approach to building HTML. I like the idea that as the framework gets more refined, it might be possible to output the same page as either HTML or XHTML with just a configuration change.

-----

1 point by hasenj 5103 days ago | link

This one is also just hacked together ;)

You have a good point about the rendering oddities with whitespace. There's a lot of room for improvement here. Rendering hints would definitely be nice. For instance, <link> and <input> tag don't typically close themselves. And tags like <a> <b> <u> <em> should just print on a single line.

> I'm definitely a fan of this 'annotate approach to building HTML. I like the idea that as the framework gets more refined, it might be possible to output the same page as either HTML or XHTML with just a configuration change.

haha, I didn't quite think of this at the beginning. My 'render-html is completely decoupled from the building of the HTML. It's just one type of a printer, and it likes to pretty-print things for readability.

We could add easily imagine adding several more printers, and even a system for registering and choosing one, with 'render-html being merely an alias to the chosen one.

    (reg-html-renderer obfuscate-render 'obfuscate')
    (set-html-renderer 'obfuscate')
ok, that might be an overkill, one could just directly say (= html-renderer obfuscate-render)

What I'm trying to say is, perhaps it's a good thing that rendering hints aren't part of the tag object.

-----

1 point by rocketnia 5103 days ago | link

...perhaps it's a good thing that rendering hints aren't part of the tag object.

I have tag-by-tag hints in mine just in case I want two otherwise identical HTML tags to have different rendering behavior, for a browser hack or something. Eventually I do hope to have an 'html hint that causes the element's tag name to be looked up in the renderer's HTML specification of choice, but I may still want to leave that hint out occasionally just to have better control.

Actually, it could just be a matter of laziness. The only hint I use right now is 'collapsible, which I put on the <meta> and <link> tags so that they don't have closing tags when their contents are empty. Since it's only one hint, I haven't bothered to make a whole extensible system based on objects that represent HTML specifications.

-----

1 point by hasenj 5103 days ago | link

Oh hell, I'm not writing a complete system for html specs. Just a quick hack for the most commonly used tags and the way I think they work.

For that matter, I didn't know link and meta could have content.

I changed the rendering engine to print some tags inline and self-close some tags.

    (render-html (page 'title "Test" 'js "js.js" 'css "css.css" 
                   
                   (p "Hello world" (e 'em "emhasis!!!") "did you see that?")
                   (p "Btw, this is" (e 'a 'href "google.com" "mylink"))))


    <!doctype html>
    <html>
      <head>
        <title>Test</title>
        <script type="text/javascript" src="js.js"></script>
        <link rel="stylesheet" type="text/css" href="css.css" />
      </head>
      <body>
        <p>Hello world <em>emhasis!!!</em> did you see that?</p> 
        <p>Btw, this is <a href="google.com">mylink</a></p>
      </body>
    </html>
> I have tag-by-tag hints in mine just in case I want two otherwise identical HTML tags to have different rendering behavior

The tag object is just a hashtable, you can always hack it and insert whatever custom attributes you want, and then use these as rendering hints in the rendering engine/function.

-----

1 point by rocketnia 5103 days ago | link

Oh hell, I'm not writing a complete system for html specs.

I sort of am, but only in the very long term. It can start as a tiny type that just holds its own specific special-casing behavior, and then it can grow in complexity as complexity is needed. That said, it may be difficult to predict what behavior is specific to a single HTML specification until one's tried to provide a choice between multiple specs.

I changed the rendering engine to print some tags inline and self-close some tags.

Awesome. :D

For that matter, I didn't know link and meta could have content.

They're not supposed to. (It would be interesting to look at the DOM to see if they can in practice.) I'm talking about when I might potentially want to give them content for a weird browser-specific hack.

The tag object is just a hashtable, you can always hack it and insert whatever custom attributes you want, and then use these as rendering hints in the rendering engine/function.

Ahhh, true enough. ^_^

-----

1 point by thaddeus 5103 days ago | link

Very Nice.

> 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:

Shouldn't this:

  (e "div" 'class "myclass" "content" "content")
 
Actually be:

  (e 'div 'class "myclass" "content" "content")
? After all "div" would generate a non-string output.

-----

1 point by rocketnia 5103 days ago | link

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.

-----

1 point by thaddeus 5103 days ago | link

It's ambiguous.

When you read a piece of code like:

  (case x y (dosomething)))
Is y a symbol? or a variable name from somewhere else in the code? Unless you've memorized every macro's inner workings it's clearly open to more than one interpretation.

Where as with:

  (case x 'y (dosomething)))
you clearly know you are passing in a symbol.

-----

3 points by rocketnia 5103 days ago | link

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)
  (if condition
    #'then
    #'else)
  
  ; 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
    body1
    body2
    body3)
  
  ; 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....

-----

3 points by akkartik 5102 days ago | link

you convinced me on case. I've fixed it in my repo, but only have internet on my kindle, won't push for a week.

-----

1 point by rocketnia 5102 days ago | link

I hope you fixed it so it uses 'testify, and I hope 'testify uses 'iso. :)

-----

2 points by akkartik 5101 days ago | link

Great idea! Done. i had case using just iso.

-----

2 points by akkartik 5096 days ago | link

Pushed. https://github.com/akkartik/arc/commit/294eef69b50ed4efe4873...

-----

1 point by hasenj 5103 days ago | link

Actually the ambiguity I was referring to is about what's an attribute and what's content.

  (some-function a b c d e f g)
Vanilla arc assumes all of "a b c d e f g" are regular arguments. If you want to supply named arguments, you'd need an extra pair of parens

  (some-function a b c (d x) (g y))
I wanted to get rid of the extra parenthesis without introducing ambiguity, and do it in a manner that's consistent with the way html tags work.

Now of course, one could still write confusing code:

    (= word 'class)
    ; some pages later:
    (div word "class-name" "actual-content")
btw, (e 'div ....) also works.

-----

1 point by evanrmurphy 5103 days ago | link

> This is one thing I really dislike in arc. There's ambiguity everywhere, not just in the html libraries.

So you'd rather have the following over arc's versions?

  (= 'x 1 'y 2 'z 3)

  (let 'x 1
    (pr x))

  (with ('x 1 'y 2)
    (pr x y))

  (obj 'a 1 'b 2)
I actually wrote recently about how I love this particular idiom in arc [1]. Unless I'm misunderstanding your point.

---

[1] http://arclanguage.org/item?id=13032

-----

3 points by rocketnia 5103 days ago | link

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.

-----

2 points by waterhouse 5103 days ago | link

> 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[1].

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.

  (xdef table (lambda args
                (let ((h (make-hash-table 'equal)))
                  (if (pair? args) ((car args) h))
                  h)))
  --ac.scm
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:

  (table 
     [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) ...))
     u)
and I believe Anarki has a w/table macro that would make it look like this:

  (w/table u
     (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:

  (xdef table (lambda args
                (let ((h (make-hash-table 'equal)))
                  (define (slave xs)
                    (cond ((null? xs) 'nonce)
                          ((null? (cdr xs))
                           ((car xs) h))
                          (else (hash-table-put! h (car xs) (cadr xs))
                                (slave (cddr xs)))))
                  (slave args)
                  h)))
  ;Usage
  arc> (table)
  #hash()
  arc> (table [= _!a 1 _!b 2])
  #hash((a . 1) (b . 2))
  arc> (table 'a 1 'b 2)
  #hash((a . 1) (b . 2))
  arc> (table 'a 1 'b 2 [= _!b 3 _!c 4])
  #hash((a . 1) (b . 3) (c . 4))
[1] I mentioned these here: http://arclanguage.org/item?id=12877

-----

2 points by rocketnia 5103 days ago | link

I like all your reasoning, but I think you may have missed this post of mine: http://arclanguage.org/item?id=13236

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....

-----

1 point by waterhouse 5103 days ago | link

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]
         (* -1 car.args)
         (+ car.args (* -1 (apply + cdr.args)))))
[1] http://arclanguage.org/item?id=13172

-----

1 point by rocketnia 5102 days ago | link

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.

-----

1 point by waterhouse 5102 days ago | link

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.

-----

1 point by rocketnia 5102 days ago | link

...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[1] 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.

[1] http://arclanguage.org/item?id=13139

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.[2] 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. ^^

[2] 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. ^_^

-----

2 points by waterhouse 5103 days ago | link

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...

-----

1 point by rocketnia 5103 days ago | link

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?

-----

1 point by waterhouse 5102 days ago | link

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[1], 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[2] 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:

  (def symbol-value (x)
    ($.namespace-variable-value $.ac-global-name.x))
  
  (defset symbol-value (x)
    (w/uniq g
      (list `(,g ,x)
            `(symbol-value ,x)
            `(fn (val) (($ namespace-set-variable-value!)
                        ($.ac-global-name ,x)
                        val)
               val))))
[1] http://paulgraham.com/core.html

[2] I once did the following in a chess program:

  (each p '(r k n)
    (each a '(a b c d e f g h)
      (each n '(1 2 3 4 5 6 7 8)
        (setf (symbol-function (symb p a n))
              (fn () ...)))))

-----

1 point by rocketnia 5102 days ago | link

I needed it to do my little module system

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.

This is a point I just acknowledged but challenged at http://arclanguage.org/item?id=13266. I'm not sure entirely whether and how I (dis)believe it, so a response is welcome. ^_^

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. ^_^

working code

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. ^_^

-----

1 point by thaddeus 5102 days ago | link

For the record I prefer:

  (obj 'a 1 'b 2)
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:

  (obj (look-up-something) 1 (look-up-something-else) 2)

-----

2 points by rocketnia 5102 days ago | link

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.
        (if (.subject:testify:do.first)
          
          ; Return the result of (then).
          then
          
          #'(apply case-part-2 subject rest))
        
        ; Return the result of (first). It's the last else.
        first)))
  
  ; 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....

-----

1 point by thaddeus 5102 days ago | link

Not sure. I find the #''y hard on the eyes :)

Though it reminds me a little on how clojure handles inline anonymous functions, which I really like:

    #{+ %1 %2} ;% being the args, %1 being the first, %2 the second, and so on
wondering if something similar is possible with case:

(case x #(myfun)(dosomething))

yet still able to:

(case x 'y (dosomething))

note: Sorry if I missed something and am suggesting non-sense stuff, I haven't yet looked into the code details you provided - so I could be out to lunch.

-----

1 point by rocketnia 5102 days ago | link

In case you missed the post I referred to, here it is: http://arclanguage.org/item?id=13240

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).

-----

1 point by evanrmurphy 5101 days ago | link

> Maybe :y or %y could be the zapper (or some better-suited abstraction over setforms).

Maybe something with `=` in it?

-----

1 point by evanrmurphy 5102 days ago | link

> keys are not variable names, so I'll ask (in general) why should obj be treated so special or differently?

There is at least some precedent of special treatment. In JavaScript's object literals, for example, you can do

  {a : 1, b : 2}
instead of

  {'a' : 1, 'b' : 2}
Then again, in other languages like Ruby, the keys must be explicit symbols:

  {:a => 1, :b => 2}
I'm not sure what the prevailing trend is on this (or whether knowing so would help to inform us about which is the better design choice).

-----

4 points by thaddeus 5103 days ago | link

> So you'd rather have to write the following?

no, I prefer

  arc> (= x 1 y 2 z 3)
I'm not suggesting variable names need to be quoted.

What I don't want to do is to this:

  [1]arc> (= a x b y c z)
I prefer:

  [2]arc> (= a 'x b 'y c 'z)
(assuming x,y,z are in fact just symbols, I expect to use [2] not [1])

And I currently can do this in arc.

However there's

  (case a x (dosomething))
where x is in fact a symbol.

-----