Arc Forumnew | comments | leaders | submit | malisper's commentslogin
2 points by malisper 4012 days ago | link | parent | on: Problem with auto-uniqs

The idea behind autouniqs is that they remove some boilerplate code (ie w/uniq). The current implementation of autouniqs[0] is actually broken. The problem boils down to the fact that with the current implementation, macros will wind up using the same uniqs every time they are expanded. Say accum was implemented using autouniqs.

  (mac accum (accfn . body)
    `(withs (acc@ nil ,accfn [push _ acc@])
       ,@body
       (rev acc@)))
The intention is acc@ will be replaced with a uniq every time accum is expanded. With the current implementation of autouniqs, acc@ will be replaced with a single uniq in accum's definition. This leads to problems with nested use of accum.

  (accum a
    (accum b
      (...)))
What clojure does to solve the problem is combine "autogensyms" with its implementation of backquote. All the symbols ending in '#' inside of the same backquote are replaced with gensyms when the backquote is evaluated. While clojure's implementation is good for most things, it breaks down with any kind of nested backquotes [including `(... ,(... `(...)))] since the autogensyms will not be the same inside all of the backquotes. I think tying the autogensyms to backquote is a requirement due to the fact that every time the backquote is evaluated new gensyms would have to be put in to replace all of the autouniqs/gensyms (feel free to prove me wrong). This becomes obvious when one starts thinking about macros with nested backquotes and how they would expand. What I want is some version of autouniqs such that they completely eliminate the need for w/uniq.

[0] http://www.arclanguage.org/item?id=18235

-----

2 points by akkartik 4011 days ago | link

Yeah, I actually thought about this use case back when I wrote accum for wart. But there's no problem here since the two expansions also create lexical scopes. In unmodified anarki:

  arc> (mac foo (accfn . body)
         `(withs (acc@ nil ,accfn [push _ acc@])
            ,@body
            (rev acc@)))
  #(tagged mac #<procedure: foo>)
  arc> (foo a
         (each l '(1 2 3)
           (a:foo b
             (each m (list l (+ l 1) (+ l 2))
               b.m))))
  ((1 2 3) (2 3 4) (3 4 5))
Is there some other scenario that I'm not considering?

(I hadn't realized that w/uniq is superior to implicit gensyms in this regard. Thanks.)

-----

2 points by malisper 4011 days ago | link

Okay, so the example with accum actually does work fine, but, only due to lexical scoping. I'm still afraid of the possibility of macros using the same symbols and causing a collision. While it looks like it would be an extremely rare bug to find, it would be one of those bugs that are a real pain to debug.

-----

2 points by akkartik 4011 days ago | link

Agreed. Which is why we should pool resources and share the first time we encounter such a bug. Or even a possible such bug that we can then debug together.

For my part, I have had an eye out for it for three years and have yet to encounter such a bug. Here's my hand-wavy reasoning for why I think it can't happen: for it to happen, an outer macro would have to rely on a use of a specific gensym inside a nested macro. I can't imagine how that could happen short of actually trying to use a gensym:

  (macro1
    (macro1
      ..x23..))  ; brittlely relying on the gensym turning into *x23*
And if we are to worry about this we can worry about all gensyms anyway.

-----

2 points by malisper 4012 days ago | link | parent | on: Problem with auto-uniqs

I may have figured out a possible way to implement backquoting in such a way based off of Steele's implementation[0]. All that is needed is an addition macro that will be put outside of each backquote expansion. When that macro finally is expanded (when all of the other backquotes have been used up), it would go through and replace all of the specified symbols with a uniq symbol up to any further backquotes. This would make it possible to have the uniqs generated every time each backquote is executed.

The only problem I can think of is something like this:

  `(x@ `(x@))
How would one specify for the generated symbols for both x@ to be the same. There might already be a way to do it through use of unquoting but I'm not sure how that would interact with the backquote implementation.

[0] http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node367.html#S...

-----


He originally did write it using Common Lisp[0].

I'm not sure why he changed it though. There must have been some benefit (maybe the lisp-1 vs lisp-2) which made it easier to write arc.

[0]http://www.paulgraham.com/hundred.html

-----

2 points by malisper 4079 days ago | link | parent | on: How do you use a hash field in a macro?

The arguments to a macro aren't evaluated. Because of this, someobj has the value of the symbol oo and not the table like the way you wanted. Akkartik's explanation covers the rest of the details.

-----


javascript

-----

2 points by malisper 4089 days ago | link | parent | on: Replace loop with xloop

For some reason I thought for and while were both implemented using loop. Turns out only for is so that probably led to some confusion. I guess it would make sense to implement while with xloop. I do like the idea of moving the current for to up. What I meant originally was to replace the uses of loop with xloop.

I have to agree with you that the current loop should definitely allow variable declarations as it should be the general case of up/down. The remaining cases should be covered by while (unless we want to put the update and the test in the same place which I see no reason for).

Just a question about some of your code. How is while not infinitely recursive? Are macros substituted at runtime in wart?

Edit: I just realized that we can implement the current loop in terms of xloop.

-----

2 points by akkartik 4088 days ago | link

Ok, anarki now has a coherent set of iteration primitives. At bottom is the erstwhile xloop, now called loop: https://github.com/arclanguage/anarki/blob/96a2757190/arc.ar....

Almost all existing occurrences of rfn are now switched to use loop. For example, see the new rev: https://github.com/arclanguage/anarki/blob/96a2757190/arc.ar...

The old loop is renamed to the subtly different for which now declares its own variable. Since for is kinda imperative, it supports break and continue out of the box: https://github.com/arclanguage/anarki/blob/96a2757190/arc.ar.... So does while: https://github.com/arclanguage/anarki/blob/96a2757190/arc.ar...

I've made a subtle change to up and down -- following C, they are no longer inclusive of their second bound. Before:

  arc> (up i 0 3 prn.i)
  0
  1
  2
  3
After:

  arc> (up i 0 3 prn.i)
  0
  1
  2
I think I've caught all their callers and corrected them, but there might be stray bugs in lib/math.arc. (It's amazing how much cleaner math.arc has gotten with the saner, C-inspired bounds.)

As the piece de resistance, compare drain before (https://github.com/arclanguage/anarki/blob/426b07440bb8d8531...):

  (mac drain (expr (o eof nil))
    (w/uniq (gacc gdone gres)
      `(with (,gacc nil ,gdone nil)
	 (while (no ,gdone)
	   (let ,gres ,expr
	     (if (is ,gres ,eof)
	       (= ,gdone t)
	       (push ,gres ,gacc))))
	 (rev ,gacc))))
And after (https://github.com/arclanguage/anarki/blob/96a2757190/arc.ar...):

  (mac drain (expr (o eos nil))
    (w/uniq (gacc gres)
      `(accum ,gacc
	 (whiler ,gres ,expr ,eos
	   (,gacc ,gres)))))
I'm still concerned about changing the semantics of loop and for, especially with the prospect of updates from pg. Fortunately it's easy to roll back these changes :)

-----

3 points by akkartik 4088 days ago | link

A final, more speculative change: for loops are named, so you can break and continue multiple loops at once using the name of the loop variable. Both these fragments have identical output:

  (up i 0 3
    (up j 0 3
      (if (> j i) (break))
      (prn i " " j)))

  (up i 0 3
    (up j 0 3
      (if (> j i) (continue-i))
      (prn i " " j)))

-----

1 point by akkartik 4078 days ago | link

What do people think of how tokens looks with afn vs loop? https://github.com/arclanguage/anarki/commit/1dd646ddcb

-----

1 point by akkartik 4089 days ago | link

Yes, wart has first class macros :) I meant to bring that up when comparing while in arc and wart.

-----


There's a lesson there that's easy to forget--or ignore. It's extremely difficult to be simultaneously concerned with the end-user experience of whatever it is that you're building and the architecture of the program that delivers that experience. Maybe impossible. I think the only way to pull it off is to simply not care about the latter. Write comically straightforward code, as if you just learned to program, and go out of your way avoid wearing any kind of software engineering hat--unless what you really want to be is a software engineer, and not the designer of an experience.

While this makes sense for simple, throw away projects, it becomes much harder to continuously provide a good experience if you are not worried about the design of your code. Just look at HN for an example of keeping nice code making it easy to provide a good end-user experience[0][1].

[0]https://news.ycombinator.com/item?id=7445933

[1]https://news.ycombinator.com/item?id=7446596

-----

3 points by akkartik 4090 days ago | link

Yeah. Technical debt[1] is a real phenomenon, and it's easy to under-estimate if you just look at nascent projects. After looking at a few short-term successes you may be tempted to assume you can just do what you think they did to get their results. But that approach is susceptible to survivor bias[2]. In reality doing anything on your own is a risky endeavor, and one way to inarguably reduce risk is to get time on your side[3]. If you take on too much technical debt time starts working against you.

The real problem is that we still have only a hazy understanding of what technical debt entails. I suspect many of the things we worry about aren't the biggest sources of technical debt or incidental complexity[4]. So yes, sometimes doing a lot of conventional 'engineering' turns out to be unnecessary. But that is a case for being more thoughtful, not less.

[1] http://en.wikipedia.org/wiki/Technical_debt

[2] http://en.wikipedia.org/wiki/Survivorship_bias

[3] http://paulgraham.com/die.html

[4] http://en.wikipedia.org/wiki/No_Silver_Bullet; http://www.codequarterly.com/2011/rich-hickey

-----

2 points by malisper 4090 days ago | link

Thanks for catching the survivorship bias.

I definitely have to agree with you that being thoughtful is important. If anything that's what programming is about. Knowing which tools of those available to you are the best for each situation.

-----

3 points by akkartik 4090 days ago | link

On the other hand, he does have a point in the bit you quoted: there's a lot of different considerations one has to deal with, and you're often doing something wrong if your focus is on architecture.

My incomplete answer at this point is to try to always record the scenarios I considered when building, in hopes that it'll keep the code easy to rewrite later on without causing regressions. By taking on that burden I hope to drop the larger burden of worrying about architecture too prematurely.

This is why almost all my thinking lately has been around how to increase the surface area that can be covered by tests: http://akkartik.name/prose (especially http://akkartik.name/post/tracing-tests). Eventually I want us to be able to easily write tests for performance, concurrency, fault tolerance, UI layout, etc., etc.

-----

2 points by malisper 4091 days ago | link | parent | on: Streams

The simplest fix would be to overload scar/scdr based on the number of arguments. While it's a nice quick fix, I do not like the idea of using the same name for two different functions.

-----

1 point by akkartik 4091 days ago | link

Yeah, especially since one of them is destructive and one isn't!

If we can get regular car and cdr to handle lazy streams then this issue will be moot. I'm investigating this. I didn't understand your point 1 above regarding when to tag, so I'm trying to recreate the primes example from SICP 3.5 to better understand the issue.

Can you point me at any sample calls to your library, maybe things you tried on the commandline while you built it?

-----

2 points by rocketnia 4091 days ago | link

Now may be a good time to mention almkglor's lazy list library for Arc 2, which did extend 'car and 'cdr like you're talking about:

https://github.com/arclanguage/anarki/blob/arc2.master/lib/s...

---

Meanwhile, here's a generator library for Arc 2 by rkts: https://github.com/arclanguage/anarki/blob/arc2.master/lib/i...

And here are three relevant libraries I made as part of Lathe:

- A lazy list library, which happens to use a multimethod framework I made: https://github.com/rocketnia/lathe/blob/master/arc/orc/oiter...

- A generator library: https://github.com/rocketnia/lathe/blob/master/arc/iter.arc

- An amb operator library: https://github.com/rocketnia/lathe/blob/master/arc/amb.arc

I've never actually found much use for these, heh.

-----

1 point by akkartik 4091 days ago | link

Thanks for the links! Too bad none of them show example usage.. :) But no matter, I'll add some to this version. It's looking promising so far, I'll push it later today.

One thing I learned from malisper's code was the existence of afnwith in anarki (https://github.com/arclanguage/anarki/blob/87d986446b/lib/ut...), which is a neat alternative solution to my http://arclanguage.org/item?id=18036.

-----

1 point by akkartik 4090 days ago | link

Ok, I've turned streams into a tagged type. I had to just support them in car and cdr and carif to get common list operations to work. However, existing operations still return regular (eager) lists when you pass them lazy streams, so I followed malisper's idea of creating lazy variants that preserve laziness in the result.

I've also added unit tests for them in lib/streams.arc.t, which is my first serious attempt at using zck's nice unit-test harness with suites.

malisper, I took some liberties with your code, such as renaming the 's' prefix to 'lazy-'. I'm not attached to these things, so feel free to revert any of my changes you don't like. Thanks for a fun exercise!

I'm not sure how you're measuring overhead, but let me know if it seems slower than before.

https://github.com/arclanguage/anarki/commit/6180f0e65

-----

2 points by malisper 4090 days ago | link

Why append lazy to the beginning when we can just extend all of them to use scons when given a stream.

-----

1 point by akkartik 4090 days ago | link

Well, you need it for cons and the generator lazy-gen and other functions that don't take a stream. Maybe the others don't, but it seemed to me that sometimes it makes sense to return a list and sometimes not. So especially since functions like firstn do something useful without changing anything, maybe we should keep that behavior around. But it's just an idea. What do you think? Since you're using them for project Euler, it'll be good to hear your experiences.

-----

2 points by rocketnia 4090 days ago | link

I'm a little surprised to see 'afnwith. Anarki also has aw's 'xloop, which is the exact same thing as 'afnwith but with the "self" anaphoric variable renamed to "next".

-----

1 point by akkartik 4090 days ago | link

Ah, that's a much nicer name. I (too) found the presence of fn to be misleading since the afn is called immediately after it's created.

I've deleted afnwith from anarki: https://github.com/arclanguage/anarki/commit/a0052f031

In wart the name can be nicer still -- just loop since for does what arc's loop does, in the footsteps of classical C.

Edit 20 minutes later: I ended up going with recur instead of next, to form a loop.. recur pair. I also considered with loop.. recur to strengthen the connection with with for the alternating var/val syntax. (The other pair of keywords in wart is collect.. yield in place of accum acc.) https://github.com/akkartik/wart/commit/b7b822f4fb has has an example use, which had me hankering for absz's w/afn (though with a better name; http://arclanguage.org/item?id=10125)

Edit 25 minutes later: It turns out xloop nests surprisingly cleanly. This does what you expect:

  loop (a 0)
    loop (b 0)
      prn a " " b
      if (b < 5) (recur ++b)
    if (a < 5) (recur ++a)
Edit 29 minutes later: Oh, loop.. recur is precisely what clojure uses!

-----

3 points by shader 4082 days ago | link

I feel like we need to extend the documentation on arclanguage.github.io to include the extra libraries on anarki, to improve discoverability.

-----

2 points by malisper 4091 days ago | link

I don't know what I was thinking. Of couse we could just tag the streams when they are created with scons. If we really wanted to we could just extend all of the basic operations for working with lists.

There still is the problem of efficiency though. For what I have been using them for (project euler) efficiency is a big deal.

-----


I was going through some of the old arc posts and found an arc to javascript compiler that you might want to look into for ideas for jasper.

Post: http://www.arclanguage.org/item?id=14795

GitHub: https://github.com/arclanguage/arcnu/blob/c9642f4be0aad88398...

-----

4 points by rocketnia 4094 days ago | link

I think your second link's missing a hyphen: https://github.com/arclanguage/arc-nu/blob/c9642f4be0aad8839...

I think that's a very old version of Arc2js. There was at least one more version Pauan made after that, from scratch. Nowadays, Pauan's lisp-that-runs-on-JavaScript efforts seem to be focused on Nulan[1], which... seems to use the escodegen library[2] for JS generation.

And here I spent the weekend optimizing my own lisp-to-JS compiler, when I could've used escodegen. :-p Ah well, it'll probably be worth it because I'm very picky about stack overflows and such.

Well, that old version of Arc2js seems to be trapped in an orphaned set of commits that GitHub might garbage-collect at any time. I tried making a local mirror of the whole repo, but those commits don't come with it. Pauan probably wouldn't consider it to be code that's worth rescuing, but now's the time to figure out how to rescue it. :)

[1] https://github.com/Pauan/nulan

[2] https://github.com/Constellation/escodegen

-----

2 points by akkartik 4094 days ago | link

Crap. It's a real problem that branches aren't version controlled.

-----

2 points by akkartik 4094 days ago | link

I'm curious how you found that link, malisper.

-----

3 points by malisper 4093 days ago | link

I was just looking at some of the older posts here and came across the discussion of arc2js[0]. When that link didn't work I used Google and came across another post[1] where he mentioned an updated page and that one had a link that worked. I must have accidentally copied the broken link instead of the one to the actual page.

[0] http://arclanguage.org/item?id=14795

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

-----

2 points by ema 4093 days ago | link

I think escodegen is interesting when one wants to have source maps.

-----


Could you give us a brief explanation?

-----

5 points by ema 4095 days ago | link

I'll try.

Motivation: I want to have a lisp which, runs in the browser, interops easily with existing javaScript libraries, and is reasonably fast. clojureScript sort of is this, but its macros are kind of a kludge (clojureScript macros are written in clojure and have to be in a seperate file from the rest of the code, also hygienic only), interop isn't seamless, and I don't want to start a jvm every time I want to compile some code. A straight arc to javaScript compiler would have to be really smart to produce fast code, and interop wouldn't be seamless either. So I decided to try writing my own language.

Implementation: Since macros can call functions which were defined in the same file, the functions needed to be evaluated right after being compiled. So I couldn't just do a simple "jasper code in, javaScript code out" compiler in haskell. So there is a self hosted[1] compiler/repl.

Current status: It is already sort of usable, but I wouldn't recommend anyone else to use it for anything beyond throw away tinkering, because I still might decide to make big changes to the semantics.

Not sure if that are the things you wanted explained.

[1] Self hosted compilers are a pain in the behind, one has to mentally switch between the language version the compiler is written in and the language version the compiler is compiling and when you break you compiler you can't use it to compile the fixed version.

-----

3 points by rocketnia 4095 days ago | link

"A straight arc to javaScript compiler would have to be really smart to produce fast code, and interop wouldn't be seamless either. So I decided to try writing my own language."

"Current status: It is already sort of usable, but I wouldn't recommend anyone else to use it for anything beyond throw away tinkering, because I still might decide to make big changes to the semantics."

You could be telling the story of my recent language projects too. :-p

---

"Self hosted compilers are a pain in the behind, one has to mentally switch between the language version the compiler is written in and the language version the compiler is compiling and when you break you compiler you can't use it to compile the fixed version."

That's a really good warning to hear, because I was headed toward having a self-hosted compiler myself.

Nevertheless, I think I'm still headed in that direction. Once you or I have a self-hosted compiler, if it can compile to a platform, it can also run on that platform. That's a way to escape from the original platform you built it on: Write a compiler backend, and then just move to that platform for developing in the future. This could be a pretty nice superpower when we have the silos of C#-based Unity, Java-based Android, etc.

-----

4 points by ema 4095 days ago | link

"Once you or I have a self-hosted compiler, if it can compile to a platform, it can also run on that platform."

In theory yes, but it wouldn't be a good idea for jasper because it is a relatively thin layer on top of javaScript. So a backend for a different platform would have to emulate a lot of JS quirks, which would be complicated and produce slow code.

So if you want to make a cross platform language, it should be a thicker layer on the first platform from the beginning.

"Nevertheless, I think I'm still headed in that direction."

Always make backups of your compiler binaries or don't overwrite old compiler binaries so in case of a bug you don't end up with only one, broken, compiler binary.

-----

2 points by akkartik 4095 days ago | link

Thanks for the answer, especially the thoughts on self-hosted compilers. I'd always found myself vaguely unsatisfied with them as an outsider trying to understand them, so it's very useful to hear similar sentiments from someone who actually has experience building them.

BTW, I followed the instructions to run it and was pleasantly surprised by how painless it was.

One question I had: why does the generated js (http://ema-fox.github.io/jasper/compile.jpr.js) have long strings containing javascript code?

-----

2 points by ema 4095 days ago | link

I actually worried that it wasn't painless enough, and that I should have waited for the web repl to be done before submitting.

These long strings contain the compiled macros. They are only there for debugging. They belong in comments instead of strings, but I didn't think of it at that time.

-----

More