Arc Forumnew | comments | leaders | submit | evanrmurphy's commentslogin
1 point by evanrmurphy 5288 days ago | link | parent | on: Macros without quasiquotation

Nice choice of background color, by the way. Very lava-like. ^_^

-----

1 point by rocketnia 5287 days ago | link

It was surprisingly easy to make it any of several variants of a fruity pink-and-orange, an antique brown, or a garish red. Finally I came upon something that at least looked like it was supposed to look like lava, and yay, you noticed. XD

Come to think of it, subtle gradients might help to evoke more glowiness and dimensionality.

-----

1 point by evanrmurphy 5287 days ago | link

I wondered the same thing about gradients. ^_^ Maybe as a future refinement. :)

A couple of other things:

1. I like that you made it full screen.

2. I was pleasantly surprised you kept the same REPL pattern!

  > [input]
  [compiled]
  => [evaluated]
I tried several variations and found this to be most intuitive and readable. One thing I was on the fence about was using '>' for the input prompt instead of 'lava>'. The former just felt less cluttered to me.

-----

1 point by rocketnia 5287 days ago | link

1. I like that you made it full screen.

That part took a bunch of fiddling! XD I tried to use it on some Android browsers though, and it's not so friendly there.... Using an online REPL from a mobile device would be pretty nifty. ^_^

Hmm, jQuery-console's manual keypress checking makes it especially Android-unfriendly. A slide-out keyboard technically works, but I'd be lost without it. :/

2. I was pleasantly surprised you kept the same REPL pattern!

My original intent was to hack your REPL to work with the console. Actually, my original intent was to have an input box with "compile" and "now evaluate that" buttons, but I liked your REPL much better. :-p

One thing I was on the fence about was using '>' for the input prompt instead of 'lava>'. The former just felt less cluttered to me.

I think "lava>" would make more sense if it were possible to switch between REPLs, but ">" makes more sense now. :)

-----

1 point by evanrmurphy 5288 days ago | link | parent | on: Macros without quasiquotation

Wow, thank you so much!! This is great! I've linked to it in the README: https://github.com/evanrmurphy/lava-script/blob/9ee2b4552ff8... :D

'that' and 'thatexpr' are both great! I should include those in the command-line REPL! :)

-----

1 point by evanrmurphy 5288 days ago | link | parent | on: Macros without quasiquotation

> That is, if you already have eval

I think you've gotten to the central issue here, which I failed to explain in the OP. Not supporting quasiquote is actually just a consequence of not supporting eval.

The reason for not supporting eval is that this is a source-to-source compiler like CoffeeScript, not a run-time environment. I do not know how to support eval (and perhaps even a meaningful quote operator) without introducing run-time dependencies.

Perhaps an example can illustrate. Here's an expression with lisp on the left and javascript output on the right:

  (+ 1 2)          1+2
Now quote it. What should the output be?

  '(+ 1 2)         '1+2'

  '(+ 1 2)         ['+', 1, 2]
Both are quoted expressions in some sense; the first is something JS can eval natively, the second requires LavaScript's special eval. The second (if I'm not mistaken) is what you need for quasiquotation, but it would require introducing into the target environment a run-time dependency on LavaScript's eval. Am I making any sense?

What I was suggesting in the OP is that the lack of eval and quasiquotation doesn't actually rule out macros. Rather it forces your macro system to be of a more limited, basic templating variety, but this is still useful. For example, here's how you define def and let using the proposed system [1]:

  (mac def (name parms body...)
    (= name (fn parms @body)))

  (mac let (var val body...)
    ((fn (var) @body) val))
> I've done a couple of quasiquote implementations already, so I could probably be of some help, if you'd like.

Great, thanks! And you're already helping by talking with me about it. :)

---

[1] I actually have this working already, except for rest parameters and the @ unquote-splicing operator. Since these are used in so many macro definitions, it isn't very useful quite yet. But the proof-of-concept is there.

-----

2 points by rocketnia 5288 days ago | link

it would require introducing into the target environment a run-time dependency on LavaScript's eval

Ohhhh, nice limitation.

Technically, if your compile phase ran the code in a LavaScript-capable environment and you kept a running total of all the compiled code in order to output it at the end, then macros should be fine. They're almost never called by the compiled code (just by the thing that was compiling that code earlier), so they're just cruft. I'd be surprised if there weren't a minifier that could cut them out of the result automatically.

On the other hand, if you did that, it would mean running the code in order to set up any definitions the macros use, and thus you would have to have all your run time libraries loaded at compile time--perhaps including the DOM and such--so maybe that's not what you're going for.

Your templating macro system is something that doesn't take advantage of any run time definitions, and is therefore usable from a completely different compile-time environment. Macros that can't execute arbitrary code are pretty dull, though. Maybe what you need is a phase control macro:

  ## (This is just a sketch. Please don't bother crediting me.)
  
  realMacros = {}
  
  realMacros.atCompileTime = (body) ->
    each body, (expr) ->
      eval lc(expr)
    'null'  ## or whatever makes sense as an ignored result
  
  (->
    orig = lc
    lc = (s) ->
      if isList(s) and (s[0] of realMacros)
        realMacros[s[0]](s[1..])
      else orig(s)
  )()
From here, people oughta be able to use (atCompileTime ...) forms to work in compiler-space, where they can modify the realMacros table directly, perhaps to implement a more convenient macro definition macro.

Hope this helps. ^_^

-----

1 point by evanrmurphy 5288 days ago | link

I like your atCompileTime approach. It would seem to open up the kinds of things the compiler can do while still not requiring anything special about the run-time environment. I'm leaning toward something like this.

> Technically, if your compile phase ran the code in a LavaScript-capable environment and you kept a running total of all the compiled code in order to output it at the end, then macros should be fine.

To be sure I understand, would this be kind of like if LavaScript both compiled every expression to JavaScript (as it does now) and ran it through the atCompileTime evaluator, so as to make it available for use in the compiler space?

-----

1 point by rocketnia 5287 days ago | link

To be sure I understand, would this be kind of like if LavaScript both compiled every expression to JavaScript (as it does now) and ran it through the atCompileTime evaluator, so as to make it available for use in the compiler space?

That and atCompileTime were almost independent trains of thought, but yeah, I think you understand it. The compiler would compile a command, write the compiled command to the output file, evaluate the command itself, then repeat with the next command.

  for command in input
    let compiled = compile( command ) in
      output-file.write compiled
      execute compiled
Like I said, I don't know if this applies to your case very well, since the compiler's environment may not adequately simulate the actual application conditions.

-----

2 points by aw 5288 days ago | link

The second (if I'm not mistaken) is what you need for quasiquotation, but it would require introducing into the target environment a run-time dependency on LavaScript's eval. Am I making any sense?

Not sure :-) To put it in my own words, macros and quasiquotation are expanded at compile-time. Thus there is a compile-time dependency: in whatever language you write your macros in, you need to be able to be able to call functions written in that language from your compiler.

Take Arc as an example. A macro in Arc is an association between the macro name and a function that does the work of expanding the macro. Saying

  (mac foo ()
    `(+ 1 2))
is just a shortcut for this:

  (def expand-foo ()
    `(+ 1 2))
  (assign foo (annotate 'mac expand-foo))
now, I can write the "expand-foo" function in any language I want, as long as I can call it from the compiler. Here, I happen to have written it in Arc. But I could have written it in Scheme. Or, I could have written it in Javascript, if I had some way of calling Javascript from Arc, such as by shelling out to a Javascript interpreter. All "expand-foo" does is take one list and return another list, so I could write that in any language.

So, if you want to write full-strength macros in LavaScript, you need some way for your compiler to be able to call, during compilation, a function you've previously written in LavaScript.

Which, if LavaScript functions are compiled into Javascript, means if you can call Javascript functions from your compiler.

If you can do that, then you'll also get quasiquotation, because quasiquotation can be implemented as a macro.

If you can't call Javascript functions from your compiler, and if you want full-strength macros, then you'd need to write your macros in some language that you can call from your compiler.

-----

3 points by aw 5288 days ago | link

Oh, I was confused:

The reason for not supporting eval is that this is a source-to-source compiler like CoffeeScript, not a run-time environment.

I wasn't paying attention and thought you meant LavaScript was written in CoffeeScript...

but I'm starting to doubt that this language can (or should) support full-fledged quasiquotation

I think I'm starting to get it: it's not that you couldn't support full-fledged quasiquotation if you wanted to by writing out an implementation in Javascript, it's that it wouldn't be very useful without being able to write macros via functions written in LavaScript, which in turn would mean that you'd have to be able to support loading LavaScript programs incrementally, which would mean having the LavaScript compiler in the runtime environment.

-----

1 point by evanrmurphy 5288 days ago | link

Yes, I think that's right.

-----

1 point by evanrmurphy 5290 days ago | link | parent | on: On unhygienic macros

Ripping this apart is completely welcome, since I don't have much experience with hygienic macros. Whenever I try to use syntax-rules, I find myself frustrated by how much less straightforward it feels than simple quasiquote macros. I'm likely just ignorant, or else I need http://www.rntz.net/post/intuitive-hygienic-macros.html.

-----

2 points by akkartik 5290 days ago | link

You're probably preaching to the choir here. I have only violent agreement to add. Purity is not perfection in this regard[1].

It isn't about small codebases vs large ones, but small teams vs large ones. If you were to gradually accumulate 150k lines of code over the next 5 years I think you would probably be ok - as long as it was just you and a couple of friends. We need constraints when we don't know who we work with, when we can't choose our collaborators.

[1] Maybe in any regard? Perhaps we need a programming correctness movement to parallel political correctness. Oops, did I just Godwin this thread? :)

-----

1 point by evanrmurphy 5290 days ago | link

> You're probably preaching to the choir here.

Good point. :)

> I have only violent agreement to add.

"violent agreement" conjures up a great image, like a SuperBowl riot in the city of the winning team. ;) </silliness>

> It isn't about small codebases vs large ones, but small teams vs large ones. If you were to gradually accumulate 150k lines of code over the next 5 years I think you would probably be ok - as long as it was just you and a couple of friends. We need constraints when we don't know who we work with, when we can't choose our collaborators.

That's a great point! Even though programmers change a lot over time, we still remain relatively backwards-compatible. And even when we do find code we previously wrote to be completely unusable, there is a sort of justice about it that doesn't exist when you're cursed by someone else's legacy code.

> Perhaps we need a programming correctness movement to parallel political correctness.

Not sure what you mean by this. Like to remove the assumption that purity is positive from mainstream programming thought?

-----

1 point by akkartik 5290 days ago | link

Like to remove the assumption that purity is positive from mainstream programming thought?

Yep. Just <silliness>

-----

3 points by evanrmurphy 5298 days ago | link | parent | on: wart on win32

I'm using wart as a way to help ease me into Common Lisp. I've had difficulty before reading CL code because the names of common operators are so different from arc's (the lisp dialect I know best). wart helps by putting it in familiar terms.

-----

1 point by akkartik 5297 days ago | link

Thanks! https://github.com/akkartik/wart/commit/8b8bcdaa421057564a34... :)

-----

1 point by evanrmurphy 5299 days ago | link | parent | on: Thinking about intersperse

How about id for is and is for iso? :)

Update: After reading over the ancestor comments more carefully, I guess this wouldn't address akkartik's concern. Nonetheless, I kind of like id.

> "Isomorphic," "equivalent," "similar," and maybe even "congruent" could work

Also keep in mind the words "alike", "same" and "exact".

-----

4 points by akkartik 5299 days ago | link

id could also mean the mathematical identity function, which arc calls idfn probably because it's a lisp-1 and we want to be able to create locals called id.

Hmm, how about if id was a low-level beast that converted any object into a say ptr type that contained its address. Now instead of (is a b) you'd say:

  (iso id.a id.b)
That seems to me about the right level of verbosity for doing the things is does that iso can't do.

-----

2 points by rocketnia 5298 days ago | link

I agree with that! :)

In Java, everything uses equals() where it can, but then it's not easy to get == behavior when it matters. Technically you can use a wrapper:

  public final class Id
  {
      private Object x;
      public Id( Object x ) { this.x = x; }
      
      public int hashCode() { return System.identityHashCode( x ); }
      public boolean equals( Object obj )
          { return obj instanceof Id && ((Id)obj).x == x; }
  }
I'm digressing, but it would be so much nicer if every object kept a hard reference to its Id wrapper. That way the Id could be used as a WeakHashMap key.[1] Weak tables are one place where comparing keys for anything but reference identity makes no sense. XD

Back in terms of (iso id.a id.b), this would have the observable effect that (iso id.id.a id.id.a) would be true for all a.

[1] Thanks to the hard reference to the Id from its x, the Id itself wouldn't be collected until its x was unreachable too. A Id without such an anchor would potentially be garbage-collected much earlier than its x, and the WeakHashMap entry would be lost. ...Anyway, some of this could be solved by changing the design of WeakHashMap. :)

-----

1 point by evanrmurphy 5298 days ago | link

I like this! Would the id of a primitive just return itself?

  id.4 ; evaluates to 4 or the address where this 4 is stored?

-----

3 points by akkartik 5298 days ago | link

I think all we need is that it be a stable value when literal objects are interned. So id.4 would probably never change, and (id "a") would change in common lisp because it creates new strings each time.

-----

1 point by rocketnia 5299 days ago | link

The name "is" is just so perfect though. What we're talking about is the difference between "the same regardless of who and why you ask" and "the same if you ask the type designer." When I see something like "is" that plainly communicates sameness, I assume it's the no-matter-what version. On the other hand, saying things are "similar" doesn't entail they're identical; they might only be identical enough.

I do like "alike." It's short, and it suggests it has something to say about non-identical arguments. It's better than "like," because (like x y) could be interpreted as "x likes y" or "cause x to like y."

-----

1 point by akkartik 5299 days ago | link

"The name 'is' is just so perfect though."

To stretch my analogy to breaking point, that's like saying I wish we could build a hundred yards into the ocean, it would make such fine waterfront property :) Some things are 'prime' but not real estate.

Ok that is probably not clear. I think I'd use is or iso, but not both.[1] iso is perfect because it is short and precise and conjures up the right image. I'll probably never find a use for is. That's ok. Good names minimize cognitive overhead, but there's still some overhead. The best name is the one you don't need, and there's enough good names that we don't have to microoptimize.

---

I'm not sure which variant is "the same regardless of who and why you ask". In common lisp there's 30 years of rationalizations why this is 'intuitive':

  * (eq "a" "a")
  nil
Yet arc chose otherwise:

  arc> (is "a" "a")
  t
So clearly it's not "the same regardless of who you ask".

If you created syntax for hash-table literals you may want this to be true:

  (is {a: 0} {a: 0})
So the semantics of is are actually quite fungible. If pointer equality is a low level beast that requires knowing internal details let's not put it on a pedestal and give it a prime name.

(Again I'm less certain than I seem.)

[1] I think isa is fine though it's so similar in form because it's different enough from iso in behavior.

-----

1 point by rocketnia 5299 days ago | link

I'm not sure which variant is "the same regardless of who and why you ask".

By "the same regardless of who and why you ask," I mean something at least as picky as "the same regardless of what algorithm you try to use to distinguish them." Notice that the pickiest equality operator in a language is the only one that can satisfy that description; any pickier one would be an algorithm (well, a black-box algorithm) that acted as a counterexample.

I think 'eqv? is this operator in standard Scheme and 'eq? is this operator in any given Scheme implementation (but I dunno, maybe it's more hackish than that in practice). Meanwhile, I think 'is acts as this operator in a "standard" subset of Arc in which strings are considered immutable. (A destructive algorithm can distinguish nonempty mutable strings.) I avoid string mutation in Arc for exactly this reason.

---

So the semantics of is are actually quite fungible.

Yeah, especially once you get down to a certain point where nothing but 'is itself would allow you to compare things, then 'is is free to be however picky it wants to be. A minimally picky 'is would solve the halting problem when applied to closures, so something has to give. :)

The least arbitrary choice is to leave out 'is altogether for certain types, like Haskell does, but I dunno, for some reason I'm willing to sacrifice things like string mutation to keep 'is around. I mean, I think it's for the sake of things like cyclic data structures, uninterned symbols, and weak references, but I don't think those things need every type to have 'is.... Interesting. I might be changing my mind on this. ^_^

-----

1 point by evanrmurphy 5300 days ago | link | parent | on: Thinking about intersperse

Hmm... so you're thinking of renaming testify to fun or asfn? I don't know if testify is general enough to earn one of those names. Aren't there other common utilities that turn expressions into functions, like thunk?

Have you considered testifn?

Update: Here are some other naming possibilities you made me think of, though I'm not sure if/where they fit in:

- fnify

- fnize

- fnk (sounds like "thunk")

To be sure, I do like your asfn, I just don't think testify is the right fit for it.

-----

1 point by akkartik 5300 days ago | link

Aren't there other common utilities that turn expressions into functions, like thunk?

Well, thunk creates a function out of code, but I'm not aware of any other function that turns data into functions.

In wart testify is now generic. If it sees a non-function by default it compares, if it sees a function it returns it, and if it sees a list it checks for membership.

-----

2 points by rocketnia 5300 days ago | link

In Penknife I've been mulling over 'testify a lot, trying to figure out whether there's a way to stop special-casing the 'fn type or to make it an everyday coercion function by introducing a 'test type or renaming it to 'fnify. I think I have the start of some answers....

Recently I posted an example utility, 'copdate, that acted exactly like 'copy but called functions on the existing values to get the replacement ones. http://arclanguage.org/item?id=13652

It would be cool if 'copy itself did that. Essentially we'd check the replacement value, and if it was a function, we'd treat it as a conversion function, and otherwise we'd just use it directly. This check could be done by a satellite utility:

  (def conversionify (x)
    (case type.x fn x [do x]))
This is just as arbitrary about the 'fn type as 'testify is, and it has just as much right (IMO) to be called 'fnify. But now we have two cousins, so we can extract their common warts into another function:

  (def abehavior (x)
    (isa x 'fn))
  
  (def testify (x)
    (check x abehavior [iso x _]))  ; not using 'is here!
  
  (def conversionify (x)
    (check x abehavior [do x]))
This 'abehavior utility is naturally extensible. Its default behavior can be nil, and each of its extensions can return t under new conditions. The question "Is this type of value supposed to be idiomatic for specifying custom behaviors for everyday Arc utilities?" isn't hard to answer. A behavior will need to have a 'defcall implementation anyway, so that's a good way to decide (but not perfect, 'cause indexable data structures use 'defcall too).

  (def abehavior (x)
    nil)
  
  (made-up-rule-definer abehavior (x) (isa x 'fn)
    t)
Also, 'testify and 'conversionify have a kinda nice common semantics: Each of them coerces to the behavior "type" based on its own idea of what a normal behavior is.

Is there a way to expose a design flaw in between 'abehavior and the places it's used? What if there's a type of value that's especially good at encoding tests or conversions, but not idiomatic for specifying callbacks? In fact, you've already mentioned one:

if it sees a list it checks for membership

I totally agree. I really like how Groovy's switch statements have that behavior. XD However, what's to say a list is an idiomatic way to give a list of options but a string isn't? Well, maybe I'd go with another function:

  (def acontainer (x)
    nil)
  
  (made-up-rule-definer acontainer (x) alist.x
    t)
  
  (def testify (x)
    [iso x _])
  
  (made-up-rule-definer testify (x) abehavior.x
    x)
  
  (made-up-rule-definer testify (x) acontainer.x
    [mem-not-using-testify _ x])
However nice (or paranoid ^^ ) this setup might be, what happens if you're looking for nil? Should nil be interpreted as an unsatisfiable condition? Well, that's probably not too bad; it's up to the utility designer to say what nil does, and we're in that realm at the moment. People will just have to say (some [iso x _] foo) instead of (some x foo) if x could be nil, even if they don't expect x to be any other kind of container or behavior.

Hopefully this rant has helped you nearly as much as it's helped me. :-p

-----

1 point by evanrmurphy 5300 days ago | link

> Well, thunk creates a function out of code, but I'm not aware of any other function that turns data into functions.

Ah, good point. I was glossing over the distinction.

-----

1 point by evanrmurphy 5302 days ago | link | parent | on: http.arc, web.arc, nginx?

Ah, you're right! There's also a lib/web.arc that looks completely different, so perhaps the name change was to avoid conflicts. </speculation>

-----


One superficial observation: I've noticed you tend not to stack your closing parens. For example, you have:

  (def txt-block (text)
    (flat ; some shorthand features require splicing
      (with (anon? nil ext? nil tag "p" content "")
        (map
          (re-split "\n\n" text)
          txt-html-block
        )
      )
    )
  )
Instead of:

  (def txt-block (text)
    (flat ; some shorthand features require splicing
      (with (anon? nil ext? nil tag "p" content "")
        (map
          (re-split "\n\n" text)
          txt-html-block))))
Of course, you're welcome to format your code however you like. However, you'll notice most people here (and in other lisp communities, I think) would choose the latter. I like it better because it shortens code and most editors make it easy to check who a paren is paired with if you're ever in doubt.

Thanks for sharing your code.

-----

1 point by dpkendal 5300 days ago | link

I've seen the latter style in other people's code, and I do indeed use it for shorter functions with small amounts of indentation. I use BBEdit, which does have a balance option, (Command-B) but I still like to be able to see when something's out of line at a glance with longer functions. I don't mind either style, though.

-----

1 point by evanrmurphy 5303 days ago | link | parent | on: html2.arc

Thanks. I started to use your to-stderr when I noticed this similar function in arc.arc:

  (def ero args
    (w/stdout (stderr) 
      (each a args 
        (write a)
        (writec #\space))
      (writec #\newline))
    (car args))
I'll probably use it instead just to keep this lib's dependencies to the arc3.1 core. I agree it would make sense to have call-w/stderr in ac.scm, though.

> You use a variable, html-nestlev, inside a closure as a sort of hidden global variable, and use it to indicate the depth of quoting... What happens when an error or ctrl-C happens in between an (html-openq) and (html-closeq)? That might be worth handling. You might try Racket's parameters, or have a function that resets the thing.

Really good point. Need to think about this more, as I'm feeling doubtful about that whole part of the program relating to nested quotation.

Update: For those just tuning in and looking for the variable html-nestlev in the source, it's been renamed to nestlev.

-----

More