Arc Forumnew | comments | leaders | submit | rocketnia's commentslogin
2 points by rocketnia 5899 days ago | link | parent | on: Jarc 14 Released

And with that, Lathe supports Arc 3.1, Anarki, Rainbow, and Jarc 14 all in one set of libraries and without design-crippling compromises. Awesome!

Well, I'm not likely to keep you busy with bug reports anymore. :-p I wonder what's next for both of us.

-----

1 point by rocketnia 5888 days ago | link

Well, here's something:

  (mac foo (x) (prn "expanding") `',x)
  (def bar () (~~foo:idfn 2))
  (def baz () (~~foo:and 2))
Calling 'bar or 'baz shouldn't print "expanding", and defining them should. I'm using 'idfn and 'and to show that it doesn't matter if that part is a function or a macro. In fact, if it's (~~foo:foo 2), the second 'foo is expanded during compilation time but the first one isn't, and it should really be the other way around. (This shouldn't be treated as a second bug; I figure it's just a consequence of compiling the arguments to the outer 'foo, whose macroexpansion has been skipped somehow.)

There's a workaround: Use "no:" instead of "~":

  (mac foo (x) (prn "expanding") `',x)
  (def bar () (no:no:foo:idfn 2))
  (def baz () (no:no:foo:and 2))
Oddly (but nicely), this works just fine.

-----

1 point by jazzdev 5898 days ago | link

Cool. Thanks for your identifying all those areas where Jarc wasn't compatible with Arc.

I've got plenty to keep me busy. ;-)

-----


Whoa, I only just noticed that 'global has exactly the same appearance and intended effect as almkglor's 'symeval (http://arclanguage.org/item?id=7701), except with a different name (albeit one aw suggested!) and, well, a different implementation and possibly some corner-case behavior differences. Necessity is the mother of reinvention, I guess. :)

-----

1 point by rocketnia 5901 days ago | link | parent | on: Building generics in arc

(One bug is that vtables for iso is not initialized anyplace. Exercise for the reader.)

If I may:

   (mac defmethod(name type args . body)
  -  `(= ((vtables* ',name) ',type)
  +  `(= ((or= (vtables* ',name) (table)) ',type)
         (fn ,args
           ,@body)))
Does that work for you? ^_^

-----

1 point by akkartik 5901 days ago | link

I hadn't thought of that :)

The drawback is that it's every defmethod's responsibility to make sure it sets the vtable entry if necessary.

I was thinking more of wrapping the implementation of either defgeneric or defmethod in a `(do (or= ..) ...)

-----

2 points by rocketnia 5901 days ago | link | parent | on: Building generics in arc

For some friendly comparison, here's a Lathe approach, as well as a three-way pro/con list.

(As I was just about to post this, I ran into a bug in Lathe itself, and Jarc's debugger helped me track it down. Go Jarc!)

  ; We're doing this REPL-style. If this were a library, we'd wrap the
  ; whole file in (packed:using-rels-as ...) and qualify all the names
  ; we define. In an application file, either approach works, and the
  ; "packed:" and name-qualification can be dropped.
  (use-rels-as mr (+ lathe-dir* "multival/multirule.arc")
               oc (+ lathe-dir* "multival/order-contribs.arc"))
  
  ; These are utility functions that we'll use to make multival
  ; contribs with certain labels have high or low precedence.
  (let get-predicates (fn (name labels)
                        (map (fn (label)
                               [and (is !name._ name)
                                    (is !label._ label)])
                             labels))
    (def prec-labels-first (multival-name . label-names)
      (let predicates (do.get-predicates multival-name label-names)
        (apply oc.prec predicates)))
    (def prec-labels-last (multival-name . label-names)
      (let predicates (do.get-predicates multival-name label-names)
        (apply oc.prec [~some ._ predicates] predicates)))
    )
  
  ; Note that 'type is the parameter here, and 'default is a label
  ; uniquely determining this contrib among all pickle-for contribs.
  (mr:rule pickle-for type default
    nil)
  
  ; We want the default pickle-for to have the lowest precedence since
  ; it never fails.
  (prec-labels-last 'pickle-for 'default)
  
  ; We'll be labeling all our custom-iso2 contribs, even though we
  ; don't refer to all those labels. That makes it easier to customize
  ; these rules by pasting new versions into the REPL or into the
  ; application code, and it makes it possible for precedence rules to
  ; identify these rules so that, say, tables with a 'type field can
  ; have a behavior that overrides the usual table behavior.
  ;
  ; This is very similar to the Inform 7 coding guideline that all rules
  ; in libraries should be named so that they can be given exceptions.
  
  (mr:rule custom-iso2 (a b) table
    (unless (all [isa _ 'table] (list a b))
      (fail "The parameters weren't all tables."))
    (and (is len.a len.b)
         (all [custom-iso2 _.1 (do.b _.0)] tablist.a)))
  
  (mr:rule custom-iso2 (a b) cons
    (unless (and acons.a acons.b)
      (fail "The parameters weren't all cons cells."))
    (and (custom-iso2 car.a car.b) (custom-iso2 cdr.a cdr.b)))
  
  ; In order to ensure commutativity, we're going to calculate pickles
  ; for both values rather than just using the first value's pickles for
  ; both. To make an effort to ensure this doesn't make a difference,
  ; we're going to compare those pickles. Note that in the generalized
  ; 'custom-iso below, when there are ten arguments, we'll end up
  ; calculating the first argument's pickle ten times. Each of these is
  ; a potential blow to efficiency, so this implementation may miss the
  ; point of pickles altogether.
  (mr:rule custom-iso2 (a b) pickle
    (with (pa pickle-for.a pb pickle-for.b)
      (if (~custom-iso2 pa pb)
        (fail "The pickles didn't match.")
          pa
        (custom-iso2 pa.a pb.b)
        (fail "The parameters didn't have pickles."))))
  
  (mr:rule custom-iso2 (a b) is
    (unless (is a b)
      (fail "The parameters weren't reference-identical."))
    t)
  
  (mr:rule custom-iso2 (a b) default
    nil)
  
  ; We want the default custom-iso2 to have the lowest precedence.
  (prec-labels-last 'custom-iso2 'default)
  
  ; It's probably more efficient to test for (is a b) first, so we'll
  ; do that.
  (prec-labels-first 'custom-iso2 'is)
  
  (def custom-iso args
    (or no.args
        (let first-arg car.args
          (all [custom-iso2 first-arg _] cdr.args))))
  
  (def fn-pickle (type converter)
    (mr:rule pickle-for value      ; no label
      (unless (isa value type)
        (fail:+ "The parameter wasn't a " type "."))
      converter))
  
  (mac pickle (type converter)
    `(fn-pickle ',type ,converter))
Pros for your approach only:

- Yours is still the only one with hash-table lookup. Since that seems to be the reason behind your approach (versus 'extend) in the first place, 'extend and this approach just don't compete. Then again, the number of extensions is likely to be small and constant during the run of a single application, and when that's true, I think the complexity issue is moot--that it's more of a numeric issue.

Cons for your approach only:

- Among your approach, this approach, and 'extend, yours is the only one that can't "distinguish arbitrary properties," as you say. Even to fix this like you suggest, by making the hash-to-a-symbol function customizable, you only get to have one function, and any one function isn't likely to support every conceivable property at once, unless it's Turing-complete or something or it's extensible itself.

- If you try applying your approach to a multimethod with arguments of multiple types, you'll end up pickling all the arguments using the first argument's pickle, and that might not be what you want.

Pros for this approach only:

- Precedence rules can be defined among extensions. Then again, this doesn't really help unless you're already using arbitrary properties; if you only have one extension per Arc type, precedence doesn't matter much.

- This may be slightly more efficient, since the 'is test is done only once and with high precedence. It would be awkward to have both high-precedence and low-precedence default behavior under your approach or the 'extend approach, and while your approach supports the possibility that every computation-intensive extension will call 'is first itself, that makes those extensions a bit more long-winded.

- This approach defines 'custom-iso rather than replacing 'iso outright, which means that code relying on 'iso to fail for tables can still work. That said, it's really simple to apply your approach or the 'extend approach to a 'custom-iso function, and in case you think this is an anti-feature, it's just as simple to apply my approach to 'iso itself.

Cons for this approach only:

- This approach is much longer than yours or 'extend, and it relies on a fair amount of other library code to boot.

Pros for 'extend only:

- It's a natural progression of the (let old-foo foo (def foo ...)) pattern that's rather fundamental to Arc's hackability. IMO, this makes 'extend inherently easy to get the hang of as an abbreviation, even if it has additional features that make it a less lightweight concept overall, such as the ability to delete extensions.

Cons for 'extend only:

- Among these three approaches, the 'extend approach is the one for which it's most important to pay attention to the order in which extensions occur in the code (or on the REPL) for efficiency and precedence purposes.

-----

4 points by aw 5898 days ago | link

Among these three approaches, the 'extend approach is the one for which it's most important to pay attention to the order in which extensions occur in the code (or on the REPL) for efficiency and precedence purposes.

Here's my personal take on when to use 'extend vs. something else:

If there's a facility that does what you need, use it, but if there isn't, use 'extend ^_^

'extend is a hack, or rather, a tool for hacking. I mean that in a good way, I use it all the time. But pile too many hacks on top of each other and you get a mess.

So if you're extending a function in a way that can be done with generics, that's good, because now you can do your definitions in an independent order and you're not wondering what hacks are going to be messing up your hack.

-----

1 point by akkartik 5901 days ago | link

Nice exagesis, thanks.

"Even to fix this like you suggest, by making the hash-to-a-symbol function customizable, you only get to have one function"

I'm not sure I follow. I was thinking, for example, that defgeneric would take an optional arg that is type by default. So for example if you want an unserialize generic function, where all serialized types are lists, the function you pass in may be car. Or am I misunderstanding your objection?

-----

2 points by rocketnia 5898 days ago | link

Sure, that's what I thought you were saying. ^_^ What I'm trying to say is that, no matter whether the argument is 'type or 'car or something else, once you decide what it is, you've limited the cases that an extension can test for. Someone loading a library and wanting to write an extension for it is limited based on an assumption the library creator made, in a way that they aren't limited when using 'extend.

I'm finding your pickle idea sort of intriguing. What if there were only a single pickle function (or one per defgeneric), which was extensible, and then selecting a method worked by trying to convert the arguments over as little pickle-distance as possible before getting to something that could be matched to a parameter list? Hmm, what I mean is something like this:

  To see if a multimethod can be called without pickling:
    If the actual arguments match exactly one parameter list, yes.
    If they match none, no.
    If they match two or more parameter lists, there's an error.

  To call a multimethod:
    For N = 0 to infinity...
      For each choice of N argument indices, allowing duplicates...
        The pickled arguments are the arguments after pickling each
           chosen index, pickling multiply chosen indices multiple
           times.
        If any of the arguments couldn't be pickled that many times, try
           the next choice of N indices instead.
        If the multimethod can be called with the pickled arguments
           without pickling further, keep track of these pickled
           arguments.
      If we had no legal choices during our loop, there's an error.
      If we've kept track of more than one callable pickle combination,
        there's an error.
      If we've kept track of exactly one, make the call and stop.
This is rather horribly inefficient, but hopefully it gets my idea across.

Despite the fact that I'm bringing up this idea, I'm not sure I agree with it. Who's to say that two picklings of the first argument is a more extreme case than one pickling of the second argument? It's just a thought, I guess.

-----

2 points by rocketnia 5907 days ago | link | parent | on: Jaclyn: The Jarc Compiler

This is pretty awesome. So... I'm not trying to rain on your parade, but I noticed two things that break Lathe right away:

- When you call (split "hello" 1), it doesn't call the Arc strings.arc split function; instead it tries to call the Java String.split() method, which doesn't accept a numeric parameter. It doesn't help to say (let split ...) or (apply split "heyo" 1 nil) either. Seems like after http://www.arclanguage.org/item?id=11730 you changed your mind or something. :)

- There's a subtle difference in the implementation of setforms that messes me up. It isn't as hackable:

  (Arc 3.1)
  
  Use (quit) to quit, (tl) to return here after an interrupt.
  arc> (mac foo (x) 'bar)
  #(tagged mac #<procedure: foo>)
  arc> (setforms 'foo.0)
  ((gs1722 bar) gs1722 (fn (gs1723) (assign bar gs1723)))
  arc> (let s setforms (= setforms [do (prn "trace: " _) s._]))
  #<procedure: setforms>
  arc> (setforms 'foo.0)
  trace: foo.0
  trace: (foo 0)
  ((gs1724 bar) gs1724 (fn (gs1725) (assign bar gs1725)))
  arc>
  
  
  (Jarc)
  
  Jarc> (mac foo (x) 'bar)
  #3(tagged mac #<procedure>)
  Jarc> (setforms 'foo.0)
  ((gs20 bar) gs20 (fn (gs21) (assign bar gs21)))
  Jarc> (let s setforms (= setforms [do (prn "trace: " _) s._]))
  #<procedure>
  Jarc> (setforms 'foo.0)
  trace: foo.0
  ((gs22 bar) gs22 (fn (gs23) (assign bar gs23)))
Notice how the Arc 3.1 implementation is self-recursive and that if it's overwritten, the old version ends up calling the new version instead of itself. Jarc's worked this way for a while too, but now either you've changed your definition of 'setforms or else you've made one too many assumptions in your occasional tail-call optimization.

Neither of these issues is a big deal for me with Lathe; I can just define another function called "xsplit" or something, and I can just ssexpand the parameter of my setforms extension myself rather than relying on the recursion to do it. But until I do those things and get farther, these potential incompatibilities may be worth your attention.

I'm really optimistic about Jarc 12+. Thanks a lot, again, for all you've done. ^_^

-----

1 point by rocketnia 5906 days ago | link

I've got Lathe's buggy-jarc branch working with Jarc 12 now. Woo!

I ended up reasoning that Lathe would be better off not needing to redefine basic Arc functionality at all, so I fiddled around and got it working that way. The biggest changes are a) that the codebase now uses '=fn and '=mc in place of 'def and 'mac, and b) that namespace applications like my.foo now expand to (global 'gs1234-foo) rather than just gs1234-foo, which lets (= my.foo 2) work without the 'setforms extension I was using.

These design changes aren't in the Lathe master branch yet, but they should get there eventually; whether or not hackability is important to Jarc, I feel it's important for Lathe not to depend on it, so that it's as compatible as possible with other codebases that do depend on redefinition (or indeed, Arc implementations which don't support it).

Anyway, back to Jarc. There were only a couple more hurdles I came across. The first is that cut's optional parameter isn't really optional in Jarc; it was easy enough to just call 'cut with three parameters all the time. The other issue was more insidious: Quasiquotation is still messed up.

- Giving `,2 should result in 2 rather than (unquote 2).

- Giving `(1 `(2 ,(3 ,4))) should result in (1 (quasiquote (2 (unquote (3 4))))) rather than (1 (quasiquote (2 (unquote (3 (unquote 4)))))). This isn't even consistent with Jarc 11. :)

- The following should result in (1) rather than (2):

  (mac foo () 1)
  (def bar () `(,(foo)))  ; remember ,@ and ssyntax too
  (mac foo () 2)
  (bar)
- The following should result in (2) rather than (1):

  (def foo () `(1))
  (= ((foo) 0) 2)
  (foo)
I thought about implementing quasiquote myself as an Arc macro to give you a reference implementation, but as I started in on that I noticed just how inconsistent Arc's own quasiquotation is.

In particular, in Arc 3.1, (iso ``',,@'(x) '`',x) works, but (iso ``',@,@'(x) '`',@x) gives an error. Also, (iso ``(unquote x y) ``,x) is true, which entails that not every expression which can be quote-mutated (the way (def foo () `(1)) is mutated above) is copied verbatim from the original quasiquote form.

Given these inelegances, it's quite a bit more difficult to emulate Arc than I expected. That said, had I understood these details going in, I probably would have gotten a lot farther by now, so I hope by mentioning them I can help you out too.

-----

1 point by jazzdev 5901 days ago | link

I've just released Jarc 13 which addresses most of these issues.

- quasiquote

I've learned that quasiquote is quite a deep rabbit hole! Jarc 13 handles quasiquote much better. I wrote a Java-based qq-expand that is simpler than qq.arc. It doesn't handle dotted lists and it doesn't do any optimizing, but it does handled nested quasiquotes properly. And qq.arc is now included in Jarc 13. So if you want it, do (use 'qq).

In Jarc 13, quasiquote is now a macro instead of a special form. This makes it much easier for the interpreter and compiler to share the code for handling it. Since neither has to do any special handling for it. I was able to rip out some code from Closure and from compile.arc. Always like it when I can do that!

- split, cut and compiled functions with optional params

Fixed. Jaclyn now handles dotted destructed args too.

- strings

Jarc 13 includes strings.arc, code.arc and pprint.arc when it starts.

- eval

Unrelated to this thread, but when I "fixed" eval to ignore the lexical environment it broke the pr-tag macro in jtml.arc. Since pr-tag is a macro it didn't need to call eval anyway, so I just rewrote it.

- Changing the definition of a function after it's defined

This scenario is still problematic:

  (def foo () `(1))
  (= ((foo) 0) 2)
  (foo)
While I agree with the hackability of Arc, I think it's perhaps a problem that you can change the definition of a function after it's defined. You are essentially reaching into foo and changing the cons inside it.

The behavior of this is dependent on how you optimize qq-expand.

Arc 3.1 yeilds (2) for the above code. Jarc 13 yields (1) for the above code. Jarc 13 with (use 'qq) yields (2). Jarc 13 with (use 'qq) (toggle-optimize) yields (1)

As CatDancer pointed out in http://arclanguage.org/item?id=9962 there are multiple ways to expand quasiquote that all result in the same expression when eval'd.

Unless there's a precise definition for how `(1) should be expanded, I don't see how we can define what the output of the above code should be.

-----

1 point by rocketnia 5901 days ago | link

Cool, I'll check it out really soon.

While I agree with the hackability of Arc, I think it's perhaps a problem that you can change the definition of a function after it's defined. You are essentially reaching into foo and changing the cons inside it.

Yeah, I don't like this feature either. It makes me a bit paranoid about using quasiquote at all sometimes, since I might be unintentionally opening up parts of my macro code to being modified. I don't let that affect my coding style--I just don't usually mutate macro-expansions and stuff--but it's sort of a pet peeve.

I'm sure there's a pg post somewhere around here about it being a bit of effort to get things to work this way, but I can't find it.

Unless there's a precise definition for how `(1) should be expanded...

I think the obvious one is '(1), which is to say, replacing the 'quasiquote symbol with 'quote. If 'quasiquote is taken out of the core, then 'quote is really the only fundamental form left that you can "reach into" this way. Furthermore, Jarc 12 already worked for '(1):

  (def foo () '(1))
  (= ((foo) 0) 2)
  (foo)            ; results in 2
If (use 'qq) gives you something that yields (2) and it's a macro, then it probably expands `(1) into '(1), and so the work is probably done for you.

This probably also means that your optimizer stops (quote (1)) from returning a reference to a single mutable (1) and makes it cons up a whole new (1) instead. If that's the case, then it isn't even much of an optimization. ^_^ I wonder if the compiled JVM bytecode can contain a sort of quote-closure field for each 'quote form, so that the compiled Java object/class can be initialized with a direct reference to a subtree of foo's syntax.

On the other hand, if this is something you don't want to fix, I'll agree with you there. :-p

-----

1 point by rocketnia 5901 days ago | link

Okay, I've brought Lathe's buggy-jarc branch up-to-date with Jarc 13. The code I had for Jarc 12 already worked, so I took the opportunity to remove all the unnecessary workarounds that were still in there. It sure is an awesome relief having the diff with the master branch get so small, but there are still two bugs in my way right now:

- Jarc's metafns aren't metafns. This has been a problem for quite a while, but usually I phrase it in terms of (my:foo ...) not working, since that's the way my namespace system encourages macro calls to look.

Each of the following should result in t rather than trying to apply 1 as a function:

  (iso ((compose do) '(1 2 3)) '(1 2 3))
  (iso ((andf do) '(1 2 3)) '(1 2 3))
For compatibility, this should work even if someone rebinds 'compose, 'andf, or some other metafn name to a completely different value. Metafn forms are identified purely by name in official Arc.

- Jarc's a&b ssyntax no longer works. The result of (ssexpand 'a&b) is (andf a b) as it should be, and (andf a b) does work correctly in most cases (the above bug being an exception), but for some reason using a&b in the code itself causes an error. It tries to look up the symbol a&b.

-----

1 point by fallintothis 5901 days ago | link

I'm sure there's a pg post somewhere around here about it being a bit of effort to get things to work this way, but I can't find it.

http://arclanguage.org/item?id=10248

-----

1 point by jazzdev 5901 days ago | link

It makes me a bit paranoid about using quasiquote

This isn't particular to quasiquote. You can reach into any function that returns a literal cons or string.

If this is something you don't want to fix, I'll agree with you there

Yeah, I'm disinclined to try to emulate Arc precisely in this regard because it appears to be an arbitrary artifact of how Arc does quasiquote expansion, and not a defined feature.

-----

2 points by fallintothis 5901 days ago | link

Yeah, I'm disinclined to try to emulate Arc precisely in this regard because it appears to be an arbitrary artifact of how Arc does quasiquote expansion, and not a defined feature.

I think the only thing going on here is quotation. Generally, quoted things work like pointers (see http://arclanguage.org/item?id=10248). Because you can still do

  arc> (def foo () '(1)) ; note: just quote, no quasiquote
  #<procedure: foo>
  arc> (= ((foo) 0) 2)
  2
  arc> (foo)
  (2)
but

  arc> (def foo () (cons 1 nil)) ; note: cons creates new data each call
  *** redefining foo
  #<procedure: foo>
  arc> (= ((foo) 0) 2)
  2
  arc> (foo)
  (1)
Then, the reason the quasiquoted thing gets weird results with the qq.arc I ported is that optimizing `(1) manages to switch between always consing new data with (list '1) and trying to reduce runtime consing with '(1).

-----

1 point by jazzdev 5899 days ago | link

I'm not (terribly) surprised that the ability to modify literals was intentional. Thanks for that link. I'm still wondering if `(1) expanding to '(1) is intentional or not.

Clearly '(foo) is a literal and `(foo ,bar) is not a literal, right? I guess it's not a huge stretch to say that `(foo) is a literal. Unfortunately, qq-expand doesn't always expand `(...) with no commas into '(...)

  Jarc> (qq-expand '(foo))
  (quote (foo))  ; literal
  Jarc> (qq-expand '(foo bar))
  (quote (foo bar)) ; literal
  Jarc> (qq-expand '(foo nil))
  (list (quote foo) nil) ; not a literal
But Arc does expand `(foo nil) into a literal

  arc> (def foo(s) (let a `(3 nil) (= (car a) (cons s (car a)))))
  #<procedure: foo>
  arc> (foo 2)
  (2 . 3)
  arc> (foo 1)
  (1 2 . 3)

-----

2 points by aw 5899 days ago | link

I'm still wondering if `(1) expanding to '(1) is intentional or not.

A quasiquote expander can expand `(1) into '(1) or (list 1), or even (join '(1) ' nil), which is what Bawden's simple, correct, but inefficient quasiquote expander does.

The expander may choose '(1) as being more efficient than (list 1), but isn't required to do so to be a quasiquote expander.

Since it's an optimization, I wouldn't write code that relies on a particular instance of `(1) evaluating to the same list every time, that's just an accidental result of the optimization. Instead, use a plain quote for that.

-----

1 point by fallintothis 5899 days ago | link

That's a bug. It didn't think that nil was a constant. Apparently I didn't know about literal 327 days ago. Thanks for finding it. :)

http://bitbucket.org/fallintothis/qq/changeset/6c0dab5f091e

(Really, all of that code needs a good rewrite. It was straight-ported from some Common Lisp, and suffers greatly for it. Bleh.)

-----

3 points by fallintothis 5899 days ago | link

There. Just updated qq.arc, removing almost 100 lines of cruft in the process. It still passes all the old tests, and (with any luck) is much more pleasant to work with.

http://bitbucket.org/fallintothis/qq/src/tip/qq.arc

-----

2 points by aw 5899 days ago | link

Yay!

-----

1 point by rocketnia 5900 days ago | link

This isn't particular to quasiquote. You can reach into any function that returns a literal cons or string.

I know, hence why I talked about 'quote. On principle, I'm equally paranoid about 'quote, 'quasiquote, and literal strings, but I've never mutated strings in my own projects, and I almost exclusively quasiquote my "escaping" cons cells rather than quoting them, so I just glossed over those facets of my paranoia. :-p

it appears to be an arbitrary artifact of how Arc does quasiquote expansion, and not a defined feature.

Like I said, I think there's a relevant pg comment around here somewhere. I've searched some more, but I still can't find it. Maybe it was just my imagination. ^_^;

Oh, wait, fallintothis's post links to it.

Arrgh, it would help if Google weren't so inconsistent. It's a result for ("behavior can be quite useful in some contexts"), but it doesn't show up in the results for ("behavior can be quite useful in some"), and ("can be quite useful in some contexts") has no results at all. Of course, observing this property and posting about it will no doubt change the search results. XD

Ah, here we go, http://af.searchyc.com/ is much nicer. ^_^

-----

1 point by aw 5906 days ago | link

I haven't looked at your quasiquote examples in particular, but note that standard Arc uses MzScheme's quasiquote expander, which is buggy for nested quasiquote expansions.

Fortunately it's easy to use a working quasiquote expander: it's a one line change to the Arc compiler so that if a "quasiquote" macro has been defined, it gets used instead of the builtin MzScheme version. Then we can load a quasiquote implementation written in Arc, such as fallintothis' port of the Common Lisp quasiquote expander.

http://arclanguage.org/item?id=9912

-----

1 point by jazzdev 5903 days ago | link

The problem with cut is the reason why 'split' wasn't working also--it calls cut without the optional parameter. Fixed now. I'm going to try fallintothis' quasiquote in Jarc. I'll need the Java-based one for bootstrapping, I think, so will need to replace this one in. Shouuld be okay. Will need some testing.

Thanks all!

-----

1 point by jazzdev 5903 days ago | link

You're not creating any rain. You're just reporting the weather, and I appreciate the information.

Yeah, even non-tail recursive functions get optimized by the compiler to avoid any run-time lookup of syms in the global environment. I'm not sure that's the right trade-off, but it seems okay for a compiler. Although hackability is important too. I'll have to think about that and research what other Lisp compilers do.

The split problems seems to be a compiler bug. I'll have to investigate that more.

And Jarc should include strings.arc, there's a conflict with java.lang.String.trim() but that should be okay.

-----

1 point by akkartik 5906 days ago | link

rocketnia: what is this lathe you keep talking about? :) I've been unable to find a link. Is it publicly available?

-----

2 points by rocketnia 5905 days ago | link

I talk about Lathe at http://arclanguage.org/item?id=11610, which links to a blog post, which links to the GitHub repo thaddeus already linked to. ^_^

The blog post is still a pretty comprehensive look at Lathe. A bit has happened since then, but the most useful thing is Jarc compatibility, and even that isn't on the master branch yet; Lathe's approach to namespaces still doesn't harmonize with Jarc as well as I'd like.

-----

1 point by akkartik 5905 days ago | link

Ah that article. Yes, I just didn't associate it with lathe.

Google couldn't find it when I searched either (lathe is in one of the comments). Arc needs a search box. /me ducks :)

-----

3 points by thaddeus 5906 days ago | link

http://github.com/rocketnia/lathe

-----

3 points by rocketnia 5917 days ago | link | parent | on: Jarc 10 Released

I made some headway moving Lathe's buggy-jarc branch forward thanks to Jarc 10, but I think I'm stuck again. Here's the sequel to that laundry list:

~~~

- The change to macros doesn't extend to ssyntax. Forms like some-macro!foo and some-macro.foo still don't use the compile-time global binding of 'some-macro. At first I thought this just affected macro!foo, but I should have known better, and the prospect of changing over a hundred instances of my.var-name into (my var-name) throughout the code, only to change them back as soon as you fixed this, was daunting enough that it's what finally burned me out.

- Saying (fn () in) errors out. The 'in macro can be replaced with the name of any macro in lexical scope (!) that requires at least one argument. As far as I can tell, the Jarc compiler compiles (fn () a b c) by compiling the expression (a b c), discarding the result, and then compiling a, b, and c as it should. Anyway, I got around this using the idiom (fn () idfn.in).

- Saying (fn ()) errors out, rather than being equivalent to (fn () nil). I actually noticed this last time, but I forgot to mention it, and the (fn () in) case reminded me.

- For whatever reason, you're calling 'quasiquote 'backquote. This didn't cause me any trouble on its own, but it did get in my way as I was "fixing" some nested quasiquotes I missed last time.

- I'm sure you know about this, but Jarc is missing a bunch of Arc 3.1 utilities. In particular, I had trouble with each's different treatment of tables and split's non-treatment of strings. I also miss arc.arc's 'down as well as strings.arc's 'tokens and 'subst. For the most part, I just copied those into the REPL as I tested and pretended they weren't a problem, but I thought I'd mention it anyway.

- Jarc doesn't recognize the space literal character when it's written as "#\ " without the quotes. (On the plus side, it does recognize "#\space". Nice!) I don't know if I like seeing "#\ " in code, but this nevertheless prevented me from simply loading Arc 3.1's strings.arc to get the functions I wanted.

~~~

The following don't actually have much to do with Lathe. They're only corner cases that came to mind just now:

~~~

- The expressions (is (obj a 1) (obj a 1)) and (iso (obj a 1) (obj a 1)) return t in Jarc even though they return nil in Arc 3.1. The first is a bug, but the second is arguably a feature, and it's consistent with Anarki.

- The result of (odd:len:copy (table) (annotate t t) t (annotate t t) t) is nil in Jarc even though it's t in Arc 3.1 (since Scheme's vectors have special 'equals? behavior).

~~~

Good luck with whatever you do next. If you fix the some-macro.foo problem, I'll be sure to try porting Lathe once again. ^_^

-----

2 points by jazzdev 5917 days ago | link

Jarc 11 has just been released which addresses:

- ssyntax is now expanded at closure time (I noticed this yesterday too, bad...) - can now parse "#\ " - fixed (fn () in) - fixed (fn ())

quasiquote is called backquote for historical reasons. I'll change this when I update Jarc to Arc 3.1

'(is (obj a 1) (obj a 1)) and (iso (obj a 1) (obj a 1))'

Yeah, the latter is useful. They both return t because they share the same code, I can't see any simple, elegant way to have the former fail and latter work.

And I wish I didn't have to choose between Arc behavior and Anarki behavior. I'll probably go with Arc semantics. Yeah, Anarki arc.arc can always be used to get Anarki semantics.

Thanks for the new test cases!

-----

1 point by rocketnia 5915 days ago | link

If there's one way Lathe's made a difference, it's by helping find test cases for Jarc. :-p That said, that role may tone down a bit because... I'm proud to announce that Lathe's buggy-jarc branch now passes all its tests! It's not so buggy anymore.

In the process of getting to that point, I did find a few more things:

- The some-macro.arg case was only partially fixed. In particular, some-macro.arg.another-arg slipped through the cracks, as well as the case where some-macro.arg is in function parameter position. All these give 2 when they should give 1:

  (mac foo (_) '[do 1])
  (def bar () foo.baz.0)
  (mac foo (_) '[do 2])
  (bar)
  
  (mac foo (_) 1)
  (def bar () (idfn foo.baz))
  (mac foo (_) 2)
  (bar)
  
  (mac foo (_) t)
  (def bar () (if foo.baz 1 2))
  (mac foo (_) nil)
  (bar)
I ended up just working around this by manually expanding the ssyntax, just like I talked about not wanting to do. ^_^ Since the majority of the cases were covered already, it wasn't very painful.

- On the other hand, when I changed some of my some-macro.arg.another-arg forms to (some-macro.arg another-arg), I got errors. That's because they were expressions sent to 'setforms, and I'm not quite sure of the nature of this problem, but apparently 'setforms checks the car of the expression, sees that the 'metafn function returns t for it (since 'some-macro.arg has ssyntax), and then tries to 'expand-metafn-call the form even though it isn't really a metafn call. I wasn't able to reproduce this on other versions of Arc, but I didn't try much. The workaround was just to put my code back the way it was, 'cause in (= some-macro.arg.another-arg foo), the ssyntax will expand all in a single step.

- Setting a table entry to nil doesn't remove it. This didn't actually get in my way, and I'm not a big fan of this part of Arc's behavior, but it makes me wonder whether Jarc has any way at all to remove table entries.

- Jarc gensyms are of the form "g1234" rather than "gs1234." This should almost never come up as a problem, but it is a difference.

- Jarc doesn't define 'sref on lists. This means you can't do (++ (q 2)), where q is a list. Since this is an example taken right out of 'enq, queues don't work either. I worked around this by redefining 'enq using (++ (car:cddr q)).

- The type of 0 is num rather than int. In order to check whether something was really an integer, I ended up doing a Java call.

- More aspects of Arc 2 popped up, like 'trunc being absent and 'accum returning its list in reverse order. I won't bother trying to make a list.

That's it for the moment. The next steps, when I get around to them, will probably be reverting Lathe's workarounds that are no longer necessary.

In the meantime, thanks a lot for responding like you have, and I hope with all this hunting for bugs you must be doing that it's still fun. :-p

-----

1 point by jazzdev 5915 days ago | link

Yeah, definitely still fun. I'll add these to the list. Glad your Lathe is working. This certainly has been useful for me (Jarc). My list for "after the compiler" is getting pretty long, but that's okay. I'm learning a lot and definitely having fun.

-----


I thought the point of 'baz was to call the first parameter with the second parameter. That is, the result of (baz "abc" 0) would be the character #\a if not for that meddling macro. Your 'baz1 is totally different.

BTW, here's my version of 'baz: (def baz (m index) do.m.index)

-----

1 point by evanrmurphy 5917 days ago | link

Yes, you are right. My 'baz1 doesn't work as intended.

-----


In my code, I've been doing sorta convoluted things to avoid accidental macro-expansion just like what you're talking about:

  (def whatever (row) (row "something"))     ; unsafe
  (def whatever (row) (do.row "something"))  ; safe
  (def whatever (row) (or.row "something"))  ; safe
  ; The "or.row" one is possibly more efficient, since it expands to
  ; "row", but I use "do.row" 'cause I don't want to desensitize myself
  ; to "or".
  
  (def whatever (tab key) (= tab.key "something"))     ; unsafe
  (def whatever (tab key) (= do.tab.key "something"))  ; doesn't work
  (def whatever (tab key) (= or.tab.key "something"))  ; confuses me
  (def whatever (tab key) (= .key.tab "something"))    ; safe
As long as I keep this up, my macros and parameters can conflict all they like, and it doesn't matter. But this approach basically amounts to qualifying the name of each parameter as it's used in function position, so from my point of view, macros and parameters almost don't live in the same namespace.

-----


I think it comes down to compile-time optimization and code reuse.

Lisp-like macros let you take code you've written and use it as data during an arbitrary compile-time calculation (which might give you code again), which you have to define. If you also want to use the compiler's own cleverness in your computation, you need another leap of technology, such as hygienic macros or some kind of explicit compiler access within the macro code.

Lambda syntax lets you take code you've written and use it as a function value, with the compilation of that function happening during compile time using the compiler's existing cleverness... and nothing but that.

Quote syntax (as well as fexprs in general) lets you take code you've written use it as data, but it does little to assure you that any of that computation will happen at compile time. If you want some intensive calculation to happen at compile time, you have to do it in a way you know the compiler (as well as any compiler-like functionality you've defined) will be nice enough to constant-propagate and inline for you.

If you don't need compile-time guarantees, then quote syntax is enough. If you're only going to reuse code as code, then lambda syntax may be enough, depending on your definition of code. If you're going to reuse code as non-code (from the compiler's perspective) and you want it compiled anyway, macros are a way to do that.

In the case of Arc, the compile time guarantee advantage is a bit flimsy considering everything's interpreted anyway, but Arc's macro system at least gives a loading time guarantee, which ought to be pretty similar for the purposes of long-running server applications.

-----


I think it's supposed to be like structure access from C-style languages, like linkedList.tail.tail.head or whatever. For instance, if you're working with a list of mailing addresses at the REPL, assuming it's structured a certain way, you can get the third digit of the fifth address's zip code by typing "addresses.4!zip.2". Using the same mechanism, you can access a list of lists as "matrix.i.j".

There are plenty of times I get frustrated that a.b.c doesn't expand to (a (b c)), but I don't think they outnumber the other case. And anyway, having a.b.c.d.e mean (a (b (c (d e))) isn't as useful when you can already say (a:b:c:d e).

-----

1 point by evanrmurphy 5920 days ago | link

Thanks for the response. Your example

  addresses.4!zip.2
reminds me of ssyntax's strict evaluation rules. I might have been tempted at some point to treat this as meaning the third zip code of the fifth address, thinking in error that . had higher precedence than !. Force of habit from arithmetic expressions with a similar shape, e.g.

  a + b / c + d
In ssyntax, everything is evaluated from left to right regardless of the operator.

-----

2 points by fallintothis 5920 days ago | link

In ssyntax, everything is evaluated from left to right regardless of the operator.

Actually, no. . and ! are evaluated left-to-right in relation to each other, but there are currently three priority levels; from highest to lowest they are: (1) : and ~, (2) . and !, (3) &. Characters within the same level needn't be evaluated left-to-right, though . and ! are. For example,

  arc> (ssexpand 'a.b:c.d) ; : takes precedence over .
  (compose a.b c.d)
  arc> (ssexpand 'a.b&c.d) ; . takes precedence over &
  ((a b&c) d)
  arc> (ssexpand 'a:b&c:d) ; : takes precedence over &
  (compose a b&c d)
Then, since & has lower precedence than ~, you can't do

  arc> even&~odd
which causes Arc to hang (which is actually a bug).

Further, though ~ and : are in the same level, : takes priority over ~ regardless of order.

  arc> (ssexpand '~a:~b)
  (compose (complement a) (complement b))
If you're interested in rewriting code to use ssyntax, you can check out my sscontract library (and let me know if it's broken!): http://arclanguage.org/item?id=11179

-----

1 point by evanrmurphy 5920 days ago | link

Very clarifying, thanks. I stand corrected. :)

-----

1 point by evanrmurphy 5911 days ago | link

> I think it's supposed to be like structure access from C-style languages

http://files.arcfn.com/doc/evaluation.html agrees with you. I may begin using it only in such contexts to help preserve the metaphor, meaning I'd stop taking shortcuts like car.xs for (car xs) because it's not an example of structure access (though xs.1 is).

-----

1 point by evanrmurphy 5911 days ago | link

s/xs.1/xs.0/ for isomorphism with (car xs).

-----

More