Arc Forumnew | comments | leaders | submit | rocketnia's commentslogin

I only just downloaded and tried out Jarc 16 (which you don't have a thread for, but you mentioned at http://arclanguage.org/item?id=11915), and I noticed this:

  Jarc> (do:compose idfn idfn)
  (stdin):1: Error: Type-error:
    compose is not of type LIST
I was using do:compose in some generated code as a way to force the macro meaning of 'compose, as opposed to the special meaning (compose ...) has in functional position.

There's an easy workaround--(do (compose ...))--and there's an easy fix too. In Jarc's utils.arc, 'expand-metafn is slightly different from arc.arc's 'expand-metafn-call: Instead of checking the caar of the remaining arguments with (iscar (car fs) 'compose), it checks the car with (is (safecar fs) 'compose). So it doesn't actually splice together nested 'compose calls; it just sometimes causes an error taking the cdr of the symbol 'compose. :)

As long as I'm looking at the source for 'expand-metafn, the if clause for 'andf seems a bit off:

        (if (car f) 'andf)
        (cons 'and (map (fn (f) (cons f args)) (cdr f)))
I think you mean (is (car f) 'andf) there. ^_^ Also, that implementation of 'andf evaluates the outer arguments a variable number of times. The Arc 3.1 version--I haven't looked at others--evaluates them exactly once by expanding like this instead:

  ((andf a b) c d)
  =>
  ((fn (gs1 gs2) (and (a gs1 gs2) (b gs1 gs2))) c d)
Furthermore, the parameters of 'if (and 'and, by extension) should be macro-expanded when the (if ...) form is compiled, but Jarc doesn't do this.

Whew! Here's a transcript highlighting some of those differences:

Jarc 16:

  Jarc> (mac foo args (prn "expanding foo with " args) `',args)
  #3(tagged mac #<procedure>)
  Jarc> (and (foo) (foo "true"))
  expanding foo with nil
  nil
  Jarc> (if (foo) (foo "true"))
  expanding foo with nil
  nil
  Jarc> (foo&foo)
  expanding foo with nil
  nil
  Jarc> (foo&foo "true")
  expanding foo with ("true")
  expanding foo with ("true")
  ("true")
  Jarc> (foo&foo (do prn!evaluating "true"))
  expanding foo with ((do prn!evaluating "true"))
  expanding foo with ((do prn!evaluating "true"))
  ((do prn!evaluating "true"))
  Jarc> (idfn&idfn (do prn!evaluating "true"))
  evaluating
  evaluating
  "true"
Arc 3.1 (but your gensyms may vary; I load Lathe in my libs.arc):

  Use (quit) to quit, (tl) to return here after an interrupt.
  arc> (mac foo args (prn "expanding foo with " args) `',args)
  #(tagged mac #<procedure: foo>)
  arc> (and (foo) (foo "true"))
  expanding foo with ()
  expanding foo with (true)
  nil
  Jarc> (if (foo) (foo "true"))
  expanding foo with ()
  expanding foo with (true)
  nil
  arc> (foo&foo)
  expanding foo with ()
  expanding foo with ()
  nil
  arc> (foo&foo "true")
  expanding foo with (gs1999)
  expanding foo with (gs1999)
  (gs1999)
  arc> (foo&foo (do prn!evaluating "true"))
  expanding foo with (gs2000)
  expanding foo with (gs2000)
  evaluating
  (gs2000)
  arc> (idfn&idfn (do prn!evaluating "true"))
  evaluating
  "true"

-----

1 point by rocketnia 5861 days ago | link | parent | on: Wish list.

Exactly. The core can't contain everything everybody would ever find useful. Instead, it's the core's job (IMO) to make it easy to define those things as they come up.

Additionally, it may or may not be the core's job to encourage canonical APIs for non-fundamental but really useful stuff, just so that programmers don't end up defining the same things over and over in ways that are flawed or incompatible with each other. I think this "may or may not" issue is the R7RS split in a nutshell.

In my own opinion, a "core" that tries to provide a comprehensive bookshelf of useful but programmer-definable things isn't a language; it's a utility library. Some libraries may be extremely popular, and they may even be standardized, distributed, and/or documented alongside their languages, but it's ultimately up to the development team to choose the combination of languages and libraries that's right for the project.

-----

1 point by rocketnia 5861 days ago | link | parent | on: Wish list.

I've actually wanted something similar from time to time, but without the '@ and 'let. Right now, I have just one place I really miss it (which you can see at http://github.com/rocketnia/lathe/blob/c343b0/arc/utils.arc), and that place currently looks like this:

  (=fn my.deglobalize-var (var)
    (zap expand var)
    (if anormalsym.var
      var
      
      ; else recognize anything of the form (global 'the-var)
      (withs (require     [unless _
                            (err:+ "An unrecognized kind of name was "
                                   "passed to 'deglobalize-var.")]
              nil         (do.require (caris var 'global))
              cdr-var     cdr.var
              nil         (do.require single.cdr-var)
              cadr-var    car.cdr-var
              nil         (do.require (caris cadr-var 'quote))
              cdadr-var   cdr.cadr-var
              nil         (do.require single.cdadr-var)
              cadadr-var  car.cdadr-var
              nil         (do.require anormalsym.cadadr-var))
        cadadr-var)
      ))
(Note that the Lathe library defines 'anormalsym to mean [and _ (isa _ 'sym) (~ssyntax _)]. Also, 'my is a Lathe namespace here, and I use Lathe's straightforward '=fn and '=mc to define things in namespaces. Finally, don't forget that (withs (nil 2) ...) binds no variables at all ('cause it destructures).)

If I define something like 'scope, I save myself a few parentheses and nils:

  (=mc my.scope body
    (withs (rev-bindings nil
            final nil
            acc [push _ rev-bindings])
      (while body
        (let var pop.body
          (if no.body
            (= final var)
              anormalsym.var
            (do do.acc.var (do.acc pop.body))
            (do do.acc.nil do.acc.var))))
      `(withs ,rev.rev-bindings ,final)))
  
  (=fn my.deglobalize-var (var)
    (zap expand var)
    (if anormalsym.var
      var
      
      ; else recognize anything of the form (global 'the-var)
      (my:scope
        require     [unless _
                      (err:+ "An unrecognized kind of name was passed "
                             "to 'deglobalize-var.")]
                    (do.require (caris var 'global))
        cdr-var     cdr.var
                    (do.require single.cdr-var)
        cadr-var    car.cdr-var
                    (do.require (caris cadr-var 'quote))
        cdadr-var   cdr.cadr-var
                    (do.require single.cdadr-var)
        cadadr-var  car.cdadr-var
                    (do.require anormalsym.cadadr-var)
                    cadadr-var)
      ))
Furthermore, I suspect this version of 'scope would almost always be preferable to 'do, 'let, and 'withs, partly because it's easier to refactor between those various cases. However, it doesn't do any destructuring, and it's probably a lot harder to pretty-print. (I'm not even sure how I would want to indent it in the long term.)

-----

2 points by fallintothis 5861 days ago | link

That example doesn't really call out to me: it looks like you could save yourself a few parentheses and nils by refactoring with something simpler, rather than with a complex macro. E.g., if I understand the code correctly:

  (=fn my.aglobal (var)
    (and (caris var 'global)
         (single:cdr var)
         (caris var.1 'quote)
         (single:cdr var.1)
         (anormalsym var.1.1)))

  (=fn my.deglobalize-var (var)
    (zap expand var)
    (if (anormalsym var)
         var
        (aglobal var)
         var.1.1
        (err "An unrecognized kind of name was passed to 'deglobalize-var.")))
But then, I avoid setting variables in sequence like that.

-----

2 points by rocketnia 5860 days ago | link

Hmm, I kinda prefer local variables over common subexpressions. It's apparently not for refactoring's sake, since I just name the variables after the way they're calculated, so it must just be a premature optimization thing. :-p

But yeah, that particular example has a few ways it can be improved. Here's what I'm thinking:

  (=fn my.deglobalize-var (var)
    (zap expand var)
    (or (when anormalsym.var var)
        (errsafe:let (global (quot inner-var . qs) . gs) var
          (and (is global 'global)
               (is quot 'quote)
               no.qs
               no.gs
               anormalsym.inner-var
               inner-var))
        (err:+ "An unrecognized kind of name was passed to "
               "'deglobalize-var.")))
I still like 'scope, but I'm fresh out of significant uses for it.

-----

2 points by fallintothis 5860 days ago | link

(To continue the digression into this particular case...) I had thought about destructuring, but found the need for qs and gs ugly. Really, a pattern matching library would be opportune. But since there's no such luck in Arc proper, it'd be ad-hoc and probably altogether not worth it (though not difficult to implement). I say this with respect to vanilla Arc; I don't know if Anarki has such a library. Still, it'd be hard to beat something like

  (=fn my.deglobalize-var (var)
    (zap expand var)
    (or (check var anormalsym)
        (when-match (global (quote ?inner-var)) var
          (check ?inner-var anormalsym))
        (err "An unrecognized kind of name was passed to 'deglobalize-var.")))

-----

3 points by rocketnia 5862 days ago | link | parent | on: Wish list.

I think your ideas have merit, but first take a look at how your examples compare to what you can already do in Arc:

  { let x 3 ; print x }
  (let x 3 prn.x)        ; Note: "a.b" effectively means "(a b)"
  
  { let x %1; print x}
  [let x _ prn.x]       ; Note: "[a b c]" is like "(fn (_) (a b c))"
  
  {let z arr[3]; print z}
  (let z arr.3 prn.z)      ; Note: "(arr 3)" is like "(cadr (cddr arr))"
  
  (call arr each (x) (print x))
  (each x arr prn.x)
As for "infix mini language" and "infix lambda mini language," I've seen a couple of infix libraries floating around, and I'm pretty confident it would only take about a page of code to make one from scratch. Mini-languages like that are what a lisp is good for. ^_^ Also, while I initially missed having infix syntax when I started out with Scheme and Arc, I eventually forgot why. Prefix syntax just takes some getting used to, I think.

That said, I am enthusiastic about mini-languages. :)

I wish you can declare strong and dynamic mode so it could be fast or dynamic.

Hmm, curious. I'm not sure I can comment much as far as lisps are concerned, but I can say I've been considering using Groovy++ instead of (or alongside) Groovy for video processing (via the Xuggler JVM wrapper of ffmpeg) in the future, and Groovy++ is basically just Groovy with the ability to make exactly that kind of declaration. So I agree it's a nice feature. ^_^

In an Arc context, I'm not sure a strong/weak typing or static/dynamic dispatch distinction makes much sense, but on the other hand Arc might have its own declaration possibilities.

For instance, if you declare a variable to be constant, that variable value could be hardwired into every function that uses it (which is kinda what Jarc's compiler already does). This could eliminate the need to "dispatch" through 'ar-apply. (Er, just so you're not totally lost, ylando, Jarc is an unofficial Arc implementation in Java, and 'ar-apply is a Scheme function in the official Arc implementation which does function calls on functions while also doing useful function-call-like behaviors on non-functions. For instance, above I mentioned that (arr 3) is like (cadr (cddr arr)) in Arc, and 'ar-apply is what achieves that.)

-----


Performance: for the sorts of exploratory programming I do, performance is important. For instance, one thing I did when trying to figure out the code was match all substrings of one watermark against another, to see if there were commonalities. This is O(N^3) and was tolerably fast in Python, but Arc would be too painful.

Here's a take on it. It isn't so painful, probably because it only takes about O(N^2) time. :)

  (def commonalities-at (a b bstart (o threshold 1))
    (accum acc
      (withs (stop (min len.a (- len.b bstart))
              run 0
              bank [do (unless (< run threshold)
                         (acc:list run (- _ run)))
                       (= run 0)])
        (when (< stop 0) (err "The start index was out of range."))
        (for i 0 (- stop 1)
          (if (is a.i (b:+ bstart i))
            ++.run
            bank.i))
        bank.stop)))
  
  (def commonalities (a b (o threshold 1))
    (accum acc
      (forlen bstart b
        (each (run offset) (commonalities-at a b bstart threshold)
          (acc:list run offset (+ bstart offset))))))
  
  (def show-top-commonalities (a b number-to-show (o threshold 1))
    (each (run astart bstart) (firstn number-to-show
                                (sort (fn (a b) (> a.0 b.0))
                                      (commonalities a b threshold)))
      (pr "matched " run " chars at " astart " and " bstart ": ")
      (write:cut a astart (+ astart run))
      (pr "=")
      (write:cut b bstart (+ bstart run))
      (prn)))
-

  arc> (show-top-commonalities w1 w1 10 10)
  matched 1038 chars at 0 and 0: [snip]
  matched 27 chars at 561 and 852: "CGGTAGATATCACTATAAGGCCCAGGA"="CGGTAGATATCACTATAAGGCCCAGGA"
  matched 24 chars at 621 and 927: "GTTTTTTTGCTGCGACGTCTATAC"="GTTTTTTTGCTGCGACGTCTATAC"
  matched 22 chars at 393 and 411: "TCATGACAAAACAGCCGGTCAT"="TCATGACAAAACAGCCGGTCAT"
  matched 18 chars at 447 and 504: "TGACTGTGAAACTAAAGC"="TGACTGTGAAACTAAAGC"
  matched 18 chars at 429 and 528: "TCATAATAGATTAGCCGG"="TCATAATAGATTAGCCGG"
  matched 18 chars at 546 and 1002: "AGTCGTATTCATAGCCGG"="AGTCGTATTCATAGCCGG"
  matched 16 chars at 677 and 971: "GCGGCACTAGAGCCGG"="GCGGCACTAGAGCCGG"
  matched 15 chars at 620 and 665: "AGTTTTTTTGCTGCG"="AGTTTTTTTGCTGCG"
  matched 15 chars at 318 and 465: "TACTAATGCCGTCAA"="TACTAATGCCGTCAA"
  nil
  arc> (do1 nil (time:commonalities w1 w1 10))
  time: 2494 msec.
  nil
  arc> (do1 nil (time:commonalities w1 w1))
  time: 4657 msec.
  nil
-

  arc> (show-top-commonalities w1 w2 10 10)
  matched 11 chars at 339 and 390: "GCTGTGATACT"="GCTGTGATACT"
  matched 10 chars at 799 and 843: "TAGCAATAAG"="TAGCAATAAG"
  matched 10 chars at 318 and 456: "TACTAATGCC"="TACTAATGCC"
  matched 10 chars at 338 and 575: "TGCTGTGATA"="TGCTGTGATA"
  matched 10 chars at 69 and 768: "TGATAAATAA"="TGATAAATAA"
  nil
  arc> (do1 nil (time:commonalities w1 w2 10))
  time: 1622 msec.
  nil
  arc> (do1 nil (time:commonalities w1 w2))
  time: 3264 msec.
  nil
To summarize, I started off in Arc, switched to Python when I realized it would take me way too long to figure out the DNA code using Arc, and then went back to Arc for this writeup after I figured out what I wanted to do. In other words, Python was much better for the exploratory part.

Yeah, I know just what you're talking about there. Still, it wasn't long ago that I found my ideas easiest to express in Java, so I think familiarity has an awful lot to do with it. I'm afraid even Arc's error messages can be an acquired taste. :-p

-----

2 points by rocketnia 5871 days ago | link | parent | on: JavaScript Compiler, js.arc (w/ arc.js)

Very nice! I have a few bits of feedback in no particular order. ^_^

   function join () {
       var args = list.apply (this, arguments);
       if (no (args)) {
           return nil; }
       else {
  -        (function (a) {
  +        return (function (a) {
               if (no (a)) {
  -                join.apply (this, listarray (cdr (args))); }
  +                return join.apply (this, listarray (cdr (args))); }
  -            else { return cons (car (a), join.apply (this, listarray (cdr (a)), listarray (cdr (args)))); }
  +            else { return cons (car (a), join.apply (this, [ cdr (a) ].concat (listarray (cdr (args))))); }
           }) (car (args)); }}
(I think I prefer the other version of this, though. For one thing, it uses constant stack space.)

Also, note that if you're going to define your own version of falsity (no()), then the JavaScript && and || aren't necessarily going to play along with that. They'll treat "" as false, for instance.

Speaking of which, it seems weird to me that you'd have 'if expand to a statement like this:

  arc> (js '(if a b c d e))
  if(a)b;else if(c)d;else e;
You can have it expand to an expression instead, bringing it closer to the Arc version's functionality:

  (function(){
    if(isnt(nil,a))return b;if(isnt(nil,c))return d;return e;})()
  or simply
  (isnt(nil,a)?b:isnt(nil,c)?d:e)
On another note, it would be nifty if you could use Arc macros (or even functions, with an Arc hack to put enough metadata on them) in the s-expressions sent to the compiler. But since the namespace seen by the (js ...) Arc isn't nearly the same as the namespace seen by the raw Arc, it seems like the sort of undertaking that would completely change the structure of the code.

...but issues with nested strings and dot ssyntax keep this from being possible at the moment.

Well, the Scheme reader is going to parse (a (b c).d) as a three-element list the same way as it parses (a (b c) .d). I'm not even sure what kind of type we might expect '(b c).d to be. Nevertheless, I agree it ought to work. :-p

As far as nested strings go, could you elaborate on that?

-----

2 points by akkartik 5870 days ago | link

"it would be nifty if you could use Arc macros (or even functions, with an Arc hack to put enough metadata on them) in the s-expressions sent to the compiler."

I had the same reaction.

"But since the namespace seen by the (js ...) Arc isn't nearly the same as the namespace seen by the raw Arc, it seems like the sort of undertaking that would completely change the structure of the code."

Perhaps I'm missing something. I imagine you could implement a macro, say jsdef, to store translation functions in a table and then replace (def js-fn..) with (jsdef fn..). js would then replace keywords in car position with functions before eval-ing the whole shebang. This way you wouldn't need to update js everytime you want to implement a new arc function in js. You'd also be able to avoid quoting when you don't need backquotes.

Hmm, perhaps this runs into a similar problem to my yrc (http://arclanguage.org/item?id=11880) -- since you're doing keyword replace, is there a scenario where the keyword won't show up until after the replace step? My mind is getting bent out of shape thinking about this.

"Very nice!"

Seconded!

-----

1 point by rocketnia 5870 days ago | link

Yeah, I think essentially you could compile Arc to JavaScript approximately the same way it's compiled to Scheme. The issue I was referring to when I was talking about "the namespace[s]" is that a function may be defined in Arc proper but not in js.arc, or conversely, a function may exist on the JavaScript side (like alert()) that has no analogue in Arc. Either the library has to manage that or the programmer does, and it seems like the kind of thing a library should do.

...

Whoa, I'm now realizing just how especially intriguing this is to me; I've been getting into the thick of working on namespace organization for Blade, and this is really relevant for that. How might one go about having a language where programs may have access to significantly different core functionality depending on how they're compiled... but where it could be swapped out for a substitute implementation on a mismatched platform...

Oooh, a whole JavaScript primitive operation suite (indexing, +, -, *, certain global variables, JSON syntax, new, and so forth) could come in a parameter. When you call a 'jsdef function in Arc, the JS-runtime parameter can be obtained from some dynamic binding or global variable, and when you compile a 'jsdef function to JavaScript, references to that parameter can be given special treatment by the compiler. As for as Arc primitive operations, the Arc-to-JS compiler may be able to detect uses of those operations and translate them so they refer to some JavaScript-side global variable(s) defined in arc.js. I think this could work. ^_^

It isn't especially novel, I suppose, 'cause it still boils down to special-casing in the compiler, but at least it's limited to special-casing the JS-runtime parameter, which is part of the library itself.

To go on, I'm thinking that the primitive operations would be treated as faithfully as possible rather than approximated by similar operations in the other language. For instance, Arc numbers would be annotated strings or something in JavaScript, rather than being demoted to the width of a JavaScript number, and Arc + may throw an error in JavaScript if a bignum arithmetic library is unavailable. More language-lenient utilities can be built up on top of those. If it's annoying to come up with names for those because they clash with all the Arc-native names, well, that could indeed be a problem....

Thoughts?

-----

1 point by evanrmurphy 5871 days ago | link

Thank you for the feedback and for fixing 'join! ^_^

> Also, note that if you're going to define your own version of falsity (no()), then the JavaScript && and || aren't necessarily going to play along with that. They'll treat "" as false, for instance.

Glad you pointed this out, may save me some debugging time. :)

Speaking of creating new structures vs. using built-in ones, I have considered switching to fake conses. That is, instead of there being a separate cons object as there is now, cons, car, cdr etc. would be array manipulations that make JavaScript arrays act just (or almost) like Arc lists. Do you have an opinion on this?

Re: 'if, the current version might seem more intuitive to a JavaScripter, but your approach may be more consistent with the way I'm expanding 'do. And then there's your point, that the latter has a close 1-to-1 correspondence of expressions with Arc while the former breaks up an expression into several statements. (Is this what you meant, or have I misunderstood?)

> As far as nested strings go, could you elaborate on that?

In the first Arc Challenge attempt I posted is the line,

  (+ |\'you said: \'| foo.value)))))
The |\'you said: \'| from that is manual string escaping I'm doing because 'js naively compiles "you said: " to 'you said: ' without considering the need for backslashes. I think it's important to be able to have JavaScript within HTML within JavaScript within HTML within ... within JavaScript, and I'm not sure it's a particularly difficult string escaping problem - I just haven't gotten it working yet.

-----

2 points by rocketnia 5870 days ago | link

...array manipulations that make JavaScript arrays act just (or almost) like Arc lists. Do you have an opinion on this?

Well, to have cons produce an JavaScript array would be a bit odd. What happens to improper lists? (Well, Arc chokes on improper lists all the time, so maybe it doesn't matter. :-p )

I think it could be a good long-term idea to support both JavaScript arrays and the Arc cons type you already have. Functions like 'all, 'map, and 'join could work for both kinds of lists, and this way people wouldn't necessarily have to change the way they're using conses, and they could work closer to the metal (or whatever JS is ^_^ ) when that was more useful.

...the latter has a close 1-to-1 correspondence of expressions with Arc while the former breaks up an expression into several statements. (Is this what you meant, or have I misunderstood?)

I'm mostly just concerned that you should be able to say things like (let x (if (< foo 2) 0 foo) (* x x)). If (if ...) translates to a statement, that won't work.

|\'you said: \'|

Oh, I see now.

Yeah, the string escaping shouldn't be that difficult. I suppose the trickiest part about string escaping is remembering to do it. ^_^

-----

1 point by rocketnia 5880 days ago | link | parent | on: Rainbow bugs

Whoops, I simplified test 4 a bit too much before I posted it.

  (pr "Test 6 ")
  (= test6 (let args (list t nil 'nil () '() "" '(#\e "s")
                           '(nil "" () (t 6)))
             (list (apply string args)
                   (apply apply + "" args)
                   (apply + "" args))))
  (prn:if (iso test6 '("test6" "test6" "test6"))
    "succeeded."
          (iso test6 '("te\"s\"nil\"\"()(t 6)"
                       "te\"s\"t6"
                       "te\"s\"nil\"\"()(t 6)"))
    "failed with the result expected on Rainbow."
          (iso test6 (list "tnilnilnilnilesnilnil(t 6)"
                           (+ "t(#" #\\ "e \"s\")(t 6)")
                           (+ "t(#" #\\ "e \"s\")(nil \"\" nil (t 6))")))
    "failed with the result expected on Jarc."
    "failed with an unexpected result.")
It's probably more convoluted than it has to be, but I'm leaving it that way this time. ^_^

Note that the string literal "...\e..." crashes Rainbow and the string literal "...\\e..." (which is what I really want) behaves incorrectly on Jarc.

Also, if I paste the above code into the Rainbow REPL directly, Rainbow almost always errors out and exits. The paste works if I remove the Jarc case or the Rainbow case, and I've also found it to work if there's been quite a bit of other input during the session. There's a similar issue on Jarc (which prints a stack trace about not having enough memory to read from the input), and, well, it could have something to do with my system.

-----

1 point by jazzdev 5879 days ago | link

the string literal "...\\e..." (which is what I really want) behaves incorrectly on Jarc.

Yeah, I intentionally don't handle escaped strings in Jarc the same as Arc. One frustration in Java is using regular expressions. You have to say

  \\s*(\\d+)\\s*
when you mean

  \s*(\d+)\s*
and regex's are hard enough to read without making them more complex. So in Jarc, I decided that only \n \r \t and \\ would have a special meaning. Every other \ is treated as a literal backslash. So Jarc treats \e as the two characters \ and e. Yeah. Non-standard, non-intuitive. No argument there. But it made regex's so much nicer to read that I decided it was a worthwhile trade-off.

This has made me realize why Perl has a separate syntax for regex.

Jarc should probably have a declare to turn this behavior on and off. At least that would provide compatibility with Arc. Jarc doesn't have \x or \u syntax either, which it should.

-----

1 point by rocketnia 5878 days ago | link

Well, I don't disagree with you on most of those topics, but you missed what I was saying. Your special meaning of \\ seems to be two backslashes. ^_^

  arc> (prn "...\\e...")   ; Arc 3.1
  ...\e...
  "...\\e..."
  
  Jarc> (prn "...\\e...")  ; Jarc
  ...\\e...
  nil
Note that if you were to "fix" this, it would mean certain regexes would use "\\\\", and that sounds like it could be what you want the least. As for me, I don't mind whatever you choose as long as it's intentional. :-p

-----

1 point by jazzdev 5874 days ago | link

Yeah, \\ means \\. It's \n \r \t and \" that are treated specially.

Though I realized Jarc needs a way to enter non-printable characters, so I've added

  \f
  \a
  \e
  \0*oo*
  \x*hh*
  \u*hhhh*
None of those interfere with regular expressions. Though I still don't have an intuitive way to put \e in a string. You can do \x5ce now (in Jarc 16) but that's ugly. I guess \\ needs to be just \ when it's followed by n r t f a e " 0 x or h. That's getting a bit complex, but I guess it's intuitive. Or maybe I should just add a regular expression literal with / delimiters like Perl has.

-----

2 points by rocketnia 5885 days ago | link | parent | on: Rainbow bugs

For a second I thought there were no improvements, but then I ran Ant again. :-p Worked like a charm.

I've found another bug for you:

  (pr "Test 5 ")
  (= test5 (tostring:catch:after (throw pr!problem) pr!-free))
  (prn:case test5
    "problem-free"  "succeeded."
    "problem"       "failed with the expected result."
                    "failed with an unexpected result.")
It seems 'after doesn't do much....

-----

1 point by conanite 5881 days ago | link

Fixed, finally. Rainbow had an optimisation that prevented copying the whole stack if it was not necessary upon entering a continuation. Alas, for the sake of the tests, that particular optimisation is gone. If I can figure out how to get it back, I will. This is the somewhat violent test case I developed for continuations in combination with 'protect, based on a previous test for co-routines:

  (accum trace 
    (assign proc-A (fn (my-b)
      (trace 'proc-A-start)
      (assign inner-A (fn (n)
        (trace (sym:string 'inner-A-start- n))
        (after (assign my-b (do (trace 'pre-ccc-my-b) (ccc my-b))) (trace (sym:string 'after-ccc-my-b- n)))
        (trace 'end-inner-A)
        (if (> n 0) (after (inner-A (- n 1)) (trace (sym:string 'after-inner-A-tail-call- n))))))
     (after (inner-A 5) (trace 'after-initial-inner-A-call))))

    (assign proc-B (fn (my-a)
      (trace 'proc-B-start)
      (assign inner-B (fn (x)
        (trace 'inner-B-start)
        (after (assign my-a (do (trace 'pre-ccc-my-a) (ccc my-a))) (trace 'after-ccc-my-a))
        (trace 'end-inner-B)
        (after (inner-B 0) (trace 'after-inner-B-tail-call))))))

    (after (proc-A proc-B) (trace 'final-after)))
Here's the expected output:

  (proc-A-start inner-A-start-5 pre-ccc-my-b proc-B-start after-ccc-my-b-5
   end-inner-A inner-A-start-4 pre-ccc-my-b inner-B-start pre-ccc-my-a
   after-ccc-my-a after-ccc-my-b-4 after-inner-A-tail-call-5
   after-ccc-my-b-5 end-inner-A inner-A-start-4 pre-ccc-my-b
   after-ccc-my-b-4 after-inner-A-tail-call-5 after-ccc-my-a end-inner-B
   inner-B-start pre-ccc-my-a after-ccc-my-a after-inner-B-tail-call
   after-ccc-my-b-4 after-inner-A-tail-call-5 after-ccc-my-b-4 end-inner-A
   inner-A-start-3 pre-ccc-my-b after-ccc-my-b-3 after-inner-A-tail-call-4
   after-inner-A-tail-call-5 after-ccc-my-a end-inner-B inner-B-start
   pre-ccc-my-a after-ccc-my-a after-inner-B-tail-call
   after-inner-B-tail-call after-ccc-my-b-4 after-inner-A-tail-call-5
   after-ccc-my-b-3 end-inner-A inner-A-start-2 pre-ccc-my-b
   after-ccc-my-b-2 after-inner-A-tail-call-3 after-inner-A-tail-call-4
   after-inner-A-tail-call-5 after-ccc-my-a end-inner-B inner-B-start
   pre-ccc-my-a after-ccc-my-a after-inner-B-tail-call
   after-inner-B-tail-call after-inner-B-tail-call after-ccc-my-b-4
   after-inner-A-tail-call-5 after-ccc-my-b-2 end-inner-A inner-A-start-1
   pre-ccc-my-b after-ccc-my-b-1 after-inner-A-tail-call-2
   after-inner-A-tail-call-3 after-inner-A-tail-call-4
   after-inner-A-tail-call-5 after-ccc-my-a end-inner-B inner-B-start
   pre-ccc-my-a after-ccc-my-a after-inner-B-tail-call
   after-inner-B-tail-call after-inner-B-tail-call after-inner-B-tail-call
   after-ccc-my-b-4 after-inner-A-tail-call-5 after-ccc-my-b-1 end-inner-A
   inner-A-start-0 pre-ccc-my-b after-ccc-my-b-0 after-inner-A-tail-call-1
   after-inner-A-tail-call-2 after-inner-A-tail-call-3
   after-inner-A-tail-call-4 after-inner-A-tail-call-5 after-ccc-my-a
   end-inner-B inner-B-start pre-ccc-my-a after-ccc-my-a
   after-inner-B-tail-call after-inner-B-tail-call after-inner-B-tail-call
   after-inner-B-tail-call after-inner-B-tail-call after-ccc-my-b-4
   after-inner-A-tail-call-5 after-ccc-my-b-0 end-inner-A
   after-inner-A-tail-call-1 after-inner-A-tail-call-2
   after-inner-A-tail-call-3 after-inner-A-tail-call-4
   after-inner-A-tail-call-5 after-initial-inner-A-call final-after)
I have no idea whether it is correct; all I can say is that arc3.1 gives the same result. Mere compatibility isn't such a high standard, at least in this case :)

Thanks again for the bug, keep them coming!

-----

1 point by conanite 5885 days ago | link

Ouch!

'after works to cleanup after errors; but it's not implemented for jumping into continuations. This one is hurting, I haven't found a quick solution. Continuations are mean enough alone; in combination with 'protect I'm lost. More news later ... and thanks for the bug report!

then I ran Ant again - oh, the joy of compiled languages :)

-----

1 point by rocketnia 5887 days ago | link | parent | on: Why isn't copylist tail-recursive?

I'm working on a repackaging/refactoring of arc; will release that at some point.

I'm glad to hear that. ^_^ I was thinking about doing something similar, but I think too many of my "fixes" would change the grain of the language, so instead I've been focusing my energy into building a new language from scratch (which is something I've been wanting to do for a long time anyway).

-----

3 points by akkartik 5887 days ago | link

"instead I've been focusing my energy into building a new language from scratch"

That's awesome; perhaps we should talk. Then again, perhaps this community is small enough I should just put what I have up here sooner rather than later. Seems stupid to be working alone like an alchemist when I have the perfect community to get feedback from. Let me go first, and then perhaps you can tell me how you've been thinking about entirely different things for your language :)

I'm trying to build a substrate that people can fork off for new languages, a more structured way to monkeypatch code without breaking it. Perhaps the most crazy idea: I'm introducing a transform phase between the read and eval phases (repl -> rtepl). Now I can write operators that modify s-expressions before eval. It seems the biggest reason you need a code-walker in arc is to implement lisp-style macros without stupid phase-separation issues. The transform level seems like the simplest, cleanest way to implement mac.

When arc started out people dismissed it as a 'thin layer of macros atop PLT'. I want to make that as true as possible. As I go over arc's functions and macros, I see a few things that just macros won't fix: if and cond treat () as false; if, + and map can take any number of args; car and cdr don't barf on (); let needs fewer parens and can handle destructuring more transparently. In these cases I plan to make it easy to just replace if with my-if in the transform phase.

This exercise has had a couple of interesting effects. The first is that as I 'explode' ac.scm and move things around, I'm ending up with a rather nice separation of the core language into layers. I've started organizing my arc directory like init.d scripts with files prefixed by a number that specifies the order to load them in. I imagine the arc implementation sitting in the same directory as my app. Say 000 and 099 holds the arc implementation by convention, and 100-999 is available to arc programs[1].

A second interesting effect: I keep running into cases where I can mix scheme code into my arc and it'll just pass right through. When you write a compiler with a cond inside it you have to translate everything. If you build it as a transformation filter, the default operation is to just pass things down to the lower layer. This seems useful so far. I've had to think about the scheme substrate a lot when I hack on arc; arc feels a very leaky abstraction as a compiler. The hard part in programming is switching layers of abstraction; if an abstraction boundary is leaky enough, just taking it out entirely may make things easier. That's the hypothesis, anyway.

Swallowing this red pill changes the language radically, but in new directions. I'm not hung up on backward compatibility, but I'm hoping most of my arc code will just run atop the new arc. I just won't have to deal with nil, or to stare at _variables in error messages.

The new language has a higher learning curve if you're new to lisp. Having just ~10 files made arc much more approachable when I was starting out. I often programmed with my program in a window on one side and arc.arc open in a window next to it. A distribution with a hundred files will be more intimidating. When you make a mistake the error messages may make no sense because you passed through into scheme without realizing it. You really have to think of this as learning a dialect of PLT scheme rather than a new language.

It will be easy to port this implementation to sbcl or jvm, but code written atop it will be hard to port; it's just too easy to use non-portable features of the implementation language.

---

[1] This takes aw's hackinator to the limit. Rather than a big ball of mud and little garnishings atop it, you have a set of ingredients to mix with. You could even imagine having each file encode its dependencies as other files with lower number prefixes, so then you can have multiple implementations of 072json and 072json-akkartik may say it needs 003scheme-aw. Multiple 003's and 072's can live in the same directory; the loader would know to only run one instance of each prefix.

-----

2 points by rocketnia 5885 days ago | link

perhaps you can tell me how you've been thinking about entirely different things for your language :)

Although I'm tempted to talk about Blade, I'm going to put that off until I've responded to your ideas. It's easy for me to get carried away. :-p

I'm introducing a transform phase between the read and eval phases (repl -> rtepl). Now I can write operators that modify s-expressions before eval.

Hmm, something like this?

  arc>
    (def subrepl (transformer name)
      (zap string name)
      (catch:while t
        (on-err [pr "Error: " details]
          (fn ()
            (pr name "> ")
            (iflet (expr) (do.transformer (read))
              (prn eval.expr)
              throw!ok)))))
  #<procedure: subrepl>
  arc> (subrepl [case _ exit nil `((prn ',_))] 'subarc)
  subarc> (1 2 3)
  (1 2 3)
  (1 2 3)
  subarc> exit
  ok
  arc>
I suppose you'd have a bit more infrastructure in there so you could manage having more than one transformation operator, all defined within the same language they're transforming. But if that infrastructure is something more general than macros, then what is it? :)

...I plan to make it easy to just replace if with my-if in the transform phase.

I think that's probably my favorite part about lisps: The way they encourage code transformation.

Say 000 and 099 holds the arc implementation by convention, and 100-999 is available to arc programs.

That seems like a much crazier idea than the RTEPL is. ^_^ But with the way Arc programs are just imperative sequences of expressions, that does seem like the path of least resistance. The part where perceived code order gets soggiest is in a directory structure, IMO, and while I consider that to be a sign that order shouldn't matter (part of Blade's motivation, which also manifests in Lathe's rule precedence system), that numbering technique sure would make the order clearer.

Still, I'm reminded of programming in BASIC. Should all my library's numbers be multiples of 10 so I (or someone else) can come in later and insert another library in between two of them? :)

Hmm... I suppose what you've got there is a convention-over-configuration approach to loading. What if you went the other way around, maximizing the "set of ingredients to mix with" aspect by having lots of little files accompanied by other files full of load statements (like libs.arc), files which can be hackinatored themselves? It would be sort of intimidating to navigate the Arc folder then, since the overall order would be hidden away in a spaghetti network of files, but maybe those files could have a naming convention like "load.arc" so that they all bunch up in one place.

Well, since libs.arc is the status quo, I suppose you've probably already considered that option.

I've had to think about the scheme substrate a lot when I hack on arc; arc feels a very leaky abstraction as a compiler. The hard part in programming is switching layers of abstraction; if an abstraction boundary is leaky enough, just taking it out entirely may make things easier. That's the hypothesis, anyway.

Hmm, I've never thought about that as being the hard part of programming. I think for me, the hard part is deciding upon a sufficiently leak-free way to implement my own abstractions. Dealing with external leaks is a matter of course; living with myself is another matter. :-p

But yeah, it's the same difference. As long as you're hacking on the language, you're the one making the decisions that determine the leaks, so you're the one who has to live with yourself.

It will be easy to port this implementation to sbcl or jvm, but code written atop it will be hard to port; it's just too easy to use non-portable features of the implementation language.

Well, how easy is it to avoid using them? ^_- If all else fails, someone can probably just write a version of traditional Arc that sits on top of your version.

perhaps you can tell me how you've been thinking about entirely different things for your language :)

Back to this again.

I've been kinda quiet about Blade because a) it's currently unusable as a language, and b) it's moving along consistently enough that I don't foresee having to abandon my unfinished ideas to the wild. Soon enough, I expect to be able to demonstrate my ideas rather than just talking about them.

Basically, though, Blade is yet another language-oriented language. It may have more defining characteristics than that soon enough, but for the moment that's the part I'm focusing on. What (maybe) makes Blade special is that all Blade languages (even those defined in the s use the same namespace, and two languages can each refer to things the other language defines.

In a way, the fundamental philosophy behind Blade as a language is that Blade isn't a single language. But any body of self-interpreting code has to have some aspect to it that can be taken as fundamental though, so there has to be at least one core Blade language. Surprisingly enough, a there's-no-syntax mentality has been surprisingly effective for designing the core syntax.

Any file without a .blade extension is ignored. Any column-zero paragraph in a .blade file which doesn't begin with a [ is ignored, and the rest are scanned for [ ] structures. Any text outside [ ] brackets is ignored. The text spanned by each top-level pair of [ ] is checked to see if it matches /\[\s([^\[\]]+?)\s.\]/ (i.e. it has a bracketless, whitespace-terminated word at the beginning), and if it doesn't, it's ignored. Then the words found this way are looked up in a global namespace dedicated for this purpose, and each of those values is applied as a pure function to the declaration text itself, complete with line number information and so forth. Each result of those pure functions will then be executed as the kind of partial calculation Blade's core dependency resolver expects.

Once I get that working (just a tiny bit longer--I already have everything but the word extractor), then it's mainly a problem of writing libraries, and once I have a proof-of-concept language set up (as a library), Blade is born.

Part of Blade's there's-no-core-language philosophy also leads to the idea that there's no one meaning for a program. Once the project has been parsed, resolved, and so forth, it's just a filled-up namespace... and if there are compilation errors, it's a namespace with errors stored in it. It's up to you or the tools you're using to decide what namespaced variables are exported, logged, displayed, executed, inspected with a REPL, etc. So one aspect of Blade in the long term is that a Blade project can have really close interaction with the tools being used to compose it; with a sufficiently efficient incremental compiler, a Blade dialect can provide its own syntax highlighting, code completion, and so forth.

Enough of that. ^^; Like I said, I'm nagged by a notion to just implement the sucker instead of talking about it. Don't let that stop you from giving feedback though. :-p

If you want to see more of what I've got so far, the source is at http://github.com/rocketnia/blade. It's under the GPL 3.0 for now, but I'm planning to pick a new license for it at some point, so suggestions on that front are welcome too.

-----

3 points by akkartik 5885 days ago | link

I looked at it. A groovy project, cool. I liked this from the README:

  Like so many other languages, Blade is so many other languages in theory,
  and it's only so many other languages at the moment.
I'm having trouble understanding parent comment, though. Can you elaborate on the 'why' rather than the 'what'? Are there other examples of language-oriented languages? I looked at the wikipedia page for LOP, but what you're doing isn't about metaprogramming, is it?

---

I got stuck with my project today, so lemme just throw it up on github: http://github.com/akkartik/yrc.

It contains a reference 'minimum arc' that I started out modifying from the top down, and the more experimental yrc.scm interpreter that I started building up from the bottom up. Right now the former isn't much different from vanilla arc, and the latter doesn't do very much at all. Things to read first: Readme at http://github.com/akkartik/yrc#readme, and commit messages starting from the bottom of (currently) http://github.com/akkartik/yrc/commits/master?page=2. I basically stopped working on the top-down version when I started the bottom-up version at commit 8.

Where I got stuck with the bottom-up version: nested macro calls don't work. Check out the failing test case for building def in terms of fn.

I've basically rediscovered why macros need to know something about lisp syntax, and you can't just search and replace blindly wherever you see a macro call. It's a classic beginner error, I imagine, something undergrads have run into thousands of times when building their own little lisp interpreter in lisp.

Comments welcome on where to go from here. Perhaps I need to interleave eval and translate steps more finely (which would require building eval and the more tranditional big-bang interprete-everything structure). Perhaps I end up with a very non-lisp language where I have to 'quote' nested macros.

---

> "Still, I'm reminded of programming in BASIC. Should all my library's numbers be multiples of 10 so I (or someone else) can come in later and insert another library in between two of them? :)"

Yeah I've been imagining having to write scripts to automate creating gaps :) But it should come up less often for files than for line numbers.

It's certainly about personal preference. I like being able to run ls and see the structure of the program[1]. I don't have to update libs.arc when I add more files. I can disable a file simply with rm or mv (and restore with git co). Compare the loaders; yrc.scm seems more timeless than as.scm.

I have this idea that you can assess the complexity of a program by measuring statistical properties of the number of hunks each commit's patch has, the number of distinct places that need to be modified when doing a unit of work. Dynamic languages help keep this complexity down by allowing Aspect-oriented programming (method chaining in ruby, advice in common lisp, before-exec and after-exec in my personal arc). A new feature may modify dozens of functions but keep all the related changes spatially clustered together. Not needing to touch the loader fits into that 'minimize hunks' aesthetic.

I figure I'll try it out and see how much I like it, and if usage uncovers drawbacks. Perhaps most likely, I'll discover I need to refer to filenames somewhere. That would be killing.

[1] Even if we need hierarchies I can imagine attaching numbers to directories, and independent namespaces of numbers within them, all the way down.

-----

3 points by rocketnia 5884 days ago | link

I think I'll reply to each of your sections in a different post. That's because the reply to the first part got to be this long. XD And to make matters more sensible, I'll answer the last question first.

I looked at the wikipedia page for LOP, but what you're doing isn't about metaprogramming, is it?

Ah, yes it is, but I see how that might not have been obvious. It looks like I left part of my post unfinished.

Before: What (maybe) makes Blade special is that all Blade languages (even those defined in the s use the same namespace, and two languages can each refer to things the other language defines.

After: What (maybe) makes Blade special is that all Blade languages (even those defined in the same project) use the same namespace, and two languages can each refer to things the other language defines.

That probably still doesn't make sense, 'cause I also neglected to mention the more important part.

If I were to use one Blade dialect to write another Blade dialect (and I plan to), I could write code in the new dialect in the very same project. The dialect's code will be statically resolved in the same namespaces as its implementation is being resolved, meaning that the implementation can refer to values defined in the dialect and bootstrap itself.

Where you might write a macro in Arc, you might instead write a parser, interpreter, and so forth in Blade. You might also just mix and match, or just instantiate a new interpreter with different parameters, or, well, just write a macro. If you want to write a macro, write a macro. :-p

So metaprogramming--programming DSLs and stuff--should find itself especially well-supported in Blade. In fact, the point of Blade is for all its languages to be interchangeable DSLs like that; that's why I'm aiming to make the core language as unassuming and unobtrusive as possible.

Can you elaborate on the 'why' rather than the 'what'?

Well, it mainly comes down to my personal preference, but I can explain my preference. :) I get fed up with most languages at some point or another. I like modeling my program in the best possible way (according to my own aesthetic), and that's led to things like switching from languages without closures to languages with closures, switching from languages without continuations to languages with continuations, and so forth. There's always some feature or combination of features I miss when I program, even if I've never used them before.

With Blade, I'm hoping to make a programming experience where the abstractions I use are exactly the abstractions I intend to use, whether they're functions without side effects, functions with side effects, functions which expect tail recursion, functions which don't expect continuations, or whatever. I believe this circus of abstractions can work as long as a) they're normalized by coercion depending on their context, and b) new contexts (lexical scopes, dynamic scopes, languages, parameter annotations, etc.) are also easy to specify.

All this should amount to a language (or system of languages) that's hard to get fed up with. As a bonus for me, were I to get fed up with it anyway, I must have had a profound realization about my preference in languages.

Are there other examples of language-oriented languages?

Absolutely. ^_^ PLT Scheme[1] is a language which prides itself on hosting other languages (as the site's front page would have me believe), and XL[2], Kernel[3], and Eight[4] are all fledgling languages with the same high expectations. I'm not sure the extent to which XL provides extensible syntax, but it makes a big deal of it. Kernel and Eight are both founded on s-expressions with fexpr-style syntactic abstraction.

There's also almkglor's hl[5], a lisplike which "allows various libraries to provide their own syntax without conflicting with other libraries ... libraries which may have been written in a paradigm you are unaware of, or are even hostile to." That philosophy lines up very nicely with mine, and I believe I'd call it LOP even though almkglor seems to skip using the word "language" (a vague word anyway) in favor of "syntax" and "paradigm."

In a way it's all sort of the same thing. I can already write syntactic abstractions on top of Arc and call them languages if I want to. The thing is, it would be especially difficult for me to write the mini-languages I want to write in a way that also plays well with other Arc code, or any other language's code for that matter (as far as I can see, but I'd be glad to be proven wrong).

Instead, I'm writing a friendlier foundation, and I don't worry myself with the semantics of the language it's implemented in. I picked Groovy because it's fast, featurific, and familiar to me.

[1] http://plt-scheme.org/ (but you already knew that!)

[2] http://xlr.sourceforge.net/

[3] http://web.cs.wpi.edu/~jshutt/kernel.html

[4] http://github.com/diiq/eight

[5] http://hl-language.sourceforge.net/

-----

1 point by akkartik 5884 days ago | link

"Where you might write a macro in Arc, you might instead write a parser, interpreter, and so forth in Blade."

Oh, very cool. That was kinda what I was trying for as well: the ability to specify new kinds of semantics, and the ability to have languages coexist.

Heh, I just noticed almkglor's hl has files starting with digits as well. Perhaps I just rediscovered his idea (or an even earlier version).

-----

1 point by rocketnia 5884 days ago | link

Where I got stuck with the bottom-up version: nested macro calls don't work. ... Perhaps I need to interleave eval and translate steps more finely (which would require building eval and the more tranditional big-bang interprete-everything structure). Perhaps I end up with a very non-lisp language where I have to 'quote' nested macros.

Hmm, I thought I had a simpler fix for you, but it seems I'm stuck too. ^_^; If it comes to me, I'll be sure to post it.

---

It's certainly about personal preference. I like being able to run ls and see the structure of the program.

Interesting, I tend to consider a flat, fifty-file list as not having a lot of structure to it. In fact, I think the term "structured programming" has something to do with not having flat lists of instructions with line numbers. :-p

Really, though, I can sort of get used to them. It's a bit like being introduced to a language by reading a book; you get a little bit of the language in each page or chapter, and if you get lost, you know which direction to turn to get back to solid footing.

A new feature may modify dozens of functions but keep all the related changes spatially clustered together. Not needing to touch the loader fits into that 'minimize hunks' aesthetic.

Well, those are things we both like. ^_^

-----

2 points by evanrmurphy 5886 days ago | link

> I'm trying to build a substrate that people can fork off for new languages

I sense a common (albeit very general) theme with Readwarp. :)

> As I go over arc's functions and macros, I see a few things that just macros won't fix: if and cond treat () as false; if, + and map can take any number of args; car and cdr don't barf on (); let needs fewer parens and can handle destructuring more transparently. In these cases I plan to make it easy to just replace if with my-if in the transform phase.

Must be misunderstanding you here. I thought most of these (esp. 'nil and '() both being false, those utilities taking any number of args) were well-publicized features of Arc, not bugs. Were you perhaps referring to PLT's functions, or could you otherwise clarify please?

> I often programmed with my program in a window on one side and arc.arc open in a window next to it.

I love doing this.

Definitely an interesting project. I'm glad you decided to share about it here and sorry for not providing richer feedback on the actual idea in this comment.

-----

2 points by akkartik 5885 days ago | link

> "I thought most of these (esp. 'nil and '() both being false, those utilities taking any number of args) were well-publicized features of Arc, not bugs."

Yeah each of these is absolutely an improvement. I meant that these are the reasons arc needs to be a language with a compiler rather than a macro library. So they're the parts that are challenging to reimplement without building a monolithic cond.

-----

1 point by evanrmurphy 5886 days ago | link

> I've had to think about the scheme substrate a lot when I hack on arc; arc feels a very leaky abstraction as a compiler. The hard part in programming is switching layers of abstraction; if an abstraction boundary is leaky enough, just taking it out entirely may make things easier. That's the hypothesis, anyway.

Sounds like an accurate assessment of Arc, though I guess I've been itching for the opposite (probably more obvious) outcome: an entirely platform-independent "arc.arc" with only the handful of axioms in "ac.scm" to bootstrap and guide ports of Arc to other substrates.

I don't see any reason why we can't have both.

-----

2 points by akkartik 5885 days ago | link

Absolutely. One benefit of the transformation layer is that it allows you to grow the language. It could eventually be completely self-sufficient, but you can start with the hard/interesting parts, and other stuff can gradually be abstracted away.

As an example, ac.scm has a comment that ar-gensym is a toy implementation. I don't need to do toy implementations because I can just use PLT's gensym directly until I decide to build my own.

-----

3 points by rocketnia 5890 days ago | link | parent | on: Unsafe-def

Looks like conanite beat me to it, but I whipped up a solution too:

  (def proper (lst)
    (accum acc
      (while acons.lst
        (do.acc pop.lst))
      only.acc.lst))
  
  (def o-flat (lst)
    (mappend [if (caris _ 'o)  (list cadr._)  ; optional
                 alist._       flat._         ; destructuring
                               list._]        ; normal
             proper.lst))  ; turn rest into normal
  
  ; This returns a two-element list containing a list of all the cars of
  ; the list and the final cdr of the list. For instance,
  ; (iso (split-end '(a b . c)) '((a b) c)). For proper lists, it's the
  ; same as (split lst len.lst).
  (def split-end (lst)
    (let onset (accum acc
                 (while acons.lst
                   (do.acc pop.lst)))
      (list onset lst)))
  
  (def antidestruct (struct)
    (treewise (fn _ `(cons ,@_)) idfn struct))
  
  (def recreate-call (name arglist)
    (withs ((nonrest rest) split-end.arglist
            recreated-nonrest
              (map [if (caris _ 'o)  cadr._
                       alist._       antidestruct._
                                     _]
                   nonrest))
      `(apply ,name ,@recreated-nonrest ,rest)))
Then this should do the trick:

   (unless (bound 'unsafe-def)
     (assign unsafe-def def)
     (mac def (name args . body)
       `(unsafe-def ,name ,args
          (on-err (fn (ex) (err:string "error in "
                                       ',name
  -                                    (tostring:pr:list ,@(flat args))
  +                                    (tostring:pr:list ,@o-flat.args)
                                       "\n"
                                       (details   ex)))
                  (fn ()   ,@body)))))
   
   (mac deftimed(name args . body)
     `(do
        (def ,(symize (stringify name) "_core") ,args
           ,@body)
        (def ,name ,args
         (let t0 (msec)
  -        (ret ans ,(cons (symize (stringify name) "_core") args)
  +        (ret ans ,(recreate-call (sym:string name '_core) args)
             (update-time ,(stringify name) t0))))))
There are really two cases here, and none of the utility functions are used for both. The error message could be modified to use 'recreate-call too (in which case 'o-flat and 'proper would be orphaned), but that would change its behavior with regards to rest args and destructuring args, which already work. (Incidentally, I think conanite and I gave equivalent solutions for the error message case.)

-----

More