Arc Forumnew | comments | leaders | submit | pg's commentslogin
4 points by pg 6672 days ago | link | parent | on: Bug in pushing with nested lists?

That looks correct to me.

-----

3 points by pg 6673 days ago | link | parent | on: New version

Anyone know what I should use to work in the maximum number of OSes?

-----

3 points by ecmanaut 6673 days ago | link

Any specific reason not to use (make-directory) instead of (system "mkdir ...") and let scheme handle the portability? http://download.plt-scheme.org/doc/372/html/mzscheme/mzschem...

-----

1 point by CatDancer 6672 days ago | link

Rather bizarrely, on my Linux system at least, make-directory is creating directories with the sticky bit set... and I didn't see anywhere in the MzScheme's manual a way to set the umask. Perhaps someone else will have better luck.

If this can be fixed, the make-directory* function in the MzScheme file.ss library will usefully create intervening directories, like mkdir -p does.

-----

3 points by elibarzilay 6670 days ago | link

That was fixed in version 371.

-----

1 point by CatDancer 6672 days ago | link

mkdir -p

-----

6 points by pg 6673 days ago | link | parent | on: Lambda with a name

You probably want rfn.

-----

3 points by pg 6673 days ago | link | parent | on: Value of iteration expressions

Sounds like a good idea.

-----

2 points by pg 6673 days ago | link | parent | on: Small Idea For List Processing Functions...

I don't think I understand. Can you give me an example of function that would take advantage of an optional parameter in that position?

-----

1 point by drcode 6673 days ago | link

The basic idea is:

  arc> (def foo ((a . b))
                a)
  #<procedure:  foo>
  arc> (foo nil)
  nil
...the reason it returns nil is I think because (car nil) in arc is nil. I think this is the Right Thing overall, even though an empty list doesn't really have a car.

However, in the case of parameter destructuring we could do better, because during destructuring we could (if it worked this way) supply default optional values to have more intelligent handling if a part of a destructuring encounters an empty list and "runs out" of values to destructure. It would look like this:

  arc> (def foo (((o a 'falafel) . b))
                a)
  #<procedure: foo>

  arc> (foo '(bar baz))
  bar

  arc>(foo nil)
  falafel
The value of this would be: 1. List processing functions, among others, could tell when they run out of values for destructuring by checking for the magic optional value 2. It might be possible to get this behavior "for free" without growing the source of arc, since it seems like it might just be a generalization of current destructuring/optional support.

-----

2 points by nlavine 6672 days ago | link

Hmm. A quick check in the arc interpreter reveals that (car nil) and (cdr nil) are both nil, so you're right about that.

However, there could be another reason this is working. In your particular example, the full expanded version of the function call is

  (foo . (nil . nil)),
or in other words,

  (cons foo
           (cons nil
                     nil)).
So it really should be destructuring that way anyway.

Now, having said that, can anyone give a good reason that (car nil) and (cdr nil) are nil? It seems a little strange to me, although I can see that it helps destructuring.

-----

7 points by pg 6674 days ago | link | parent | on: Traversal

Since captured symbols are kind of freaky I want to be conservative with names: it for objs in general, and self for fns. I wouldn't want to use trav in case someone actually wants to call trav in one of the arguments.

-----

4 points by almkglor 6674 days ago | link

Well, "self" has attached itself (in my mind, anyway) to afn. Seeing "trav" use "self" is disturbing - its like "self" is an unfaithful wife or something.

"self" for "afn" is OK, because "afn" is defined as "a function which calls itself". It's not so OK for "trav", because "trav" doesn't call "itself" (yes, internally it does, but for the user of the macro, it isn't - it's traversing a structure). That's why I'd rather suggest using go. Or alternatively have an optional "go" argument:

  (mac trav (struct . body)
    (with
         (
           gofn (if (isa (car body) 'sym) (car body) 'self)
           fns (if (isa (car body) 'sym) (cdr body) body))
    (w/uniq g
      `((rfn ,gofn (,g)
          (when ,g
            ,@(map [list _ g] fs)))
        ,x))))
So:

  (trav b go [go _!l] [a _!d] [go _!r])
or:

  (trav b [self _!l] [a _!d] [go _!r])
Of course this has the problem where the user might have an existing named function that he or she just wants to call to handle some part of the structure:

   (trav b existing-fun [self _!next])
A possible alternative would be to force the user to add a ' to the gofn symbol. Since symbols cannot be applied (yet), it would unambiguously specify a special symbol to replace the default 'self:

  (mac trav (struct . body)
    (withs
         (
           is-quote-form [caris _ (car ''x)]
           quote-form-var cadr
           gofn
           (if (is-quote-form (car body))
              (quote-form-var (car body))
              'self)
           fns
           (if (is-quote-form (car body))
              (cdr body)
              body))
    (w/uniq g
      `((rfn ,gofn (,g)
          (when ,g
            ,@(map [list _ g] fs)))
        ,x))))
Then I could either use:

  (trav b [self _!l] [a _!d] [self _!r])
or:

  (trav b 'go [go _!l] [a _!d] [go _!r])
A final alternative is to imitate 'rfn and 'afn: define 'rtrav which requires a symbol for the "go" action, and 'atrav which specifically uses 'self.

-----

4 points by almkglor 6674 days ago | link

Yet another alternative:

   (mac trav ((go v) s . body)
      `((rfn ,go (,v)
            ,@body)
        ,s))

   (trav (go node) b
      (go node!l)
      (a node!d)
      (go node!r))
The above is arguably lispier, and removes the need for [] everywhere.

-----

1 point by almkglor 6673 days ago | link

Bugged, of course ^^ Forgot the (when ...) test.

   (mac trav ((go v) s . body)
      `((rfn ,go (,v)
            (when ,v
              ,@body))
        ,s))

-----

5 points by pg 6674 days ago | link | parent | on: Binary Search Trees in Arc

The Clojure version is shorter

It doesn't look to me like it's obviously shorter. I haven't tried counting nodes in both either, but in nonwhitespace chars, which should be a reasonable approximation of nodes in two languages so similar, the Arc version is slightly shorter: 755 chars vs 774.

-----


It means you've specified a bad language.

-----

2 points by soegaard 6674 days ago | link

Sure. In general it isn't so easy to figure out, whether something was done on purpose.

Example: Was it intensional that (1 . + . 1) evaluated to 2?

-----

4 points by pg 6674 days ago | link

It's rather a dishonest argument to use an example like that, because it's an artifact of bootstrapping the prototype off MzScheme. A more convincing argument would be strange behavior resulting from the way something was defined in arc.arc.

-----

9 points by kens 6674 days ago | link

Is annotate a general mechanism, or specifically for defining macros? Is ellipsize supposed to limit its output to 80 characters or display at most 80 characters of the input? Is median of an even-length list supposed to return the lower of the middle two? Is cdar supposed to be missing? Is (type 3.0) supposed to be an int? Is support for complex numbers supposed to be in Arc? Is client-ip supposed to be there, or is it left over from something? Does afn stand for "anonymous fn"? What does "rfn" stand for?

These are all real questions that I've faced while trying to write documentation. Let me make it clear that these are not rhetorical questions; I'm more interested in getting actual answers than arguing about using the code as the spec.

-----

7 points by pg 6674 days ago | link

The former; the latter; yes; yes; yes (for now); yes; the former (for now); anaphoric; recursive.

-----

6 points by parenthesis 6673 days ago | link

I've matched pg's replies up with the questions, to make this discussion easier to read:

annotate is a general mechanism

ellipsize is supposed to display at most 80 characters of the input

median of an even-length list is supposed to return the lower of the middle two

cdar is supposed to be missing

(type 3.0) is supposed to be an int (for now)

support for complex numbers is supposed to be in Arc

client-ip is supposed to be there (for now)

afn stands for "anaphoric function"

rfn stands for "recursive function"

-----

2 points by drewc 6665 days ago | link

But pg's was so much more concise! ;)

-----

4 points by eds 6673 days ago | link

>> Is cdar supposed to be missing?

> yes

Wasn't the point keeping the names car and cdr so you can compose them? (I remember reading such in one of pg's essays.) Then it seems to me to take full advantage of that you need to provide those names for use.

I don't think it is unreasonable to do the following, but it is currently not provided in Arc:

arc> (cdar '((1 2 3) (4 5 6))) Error: "reference to undefined identifier: _cdar"

Maybe this is just me missing CL's four levels of composition of car and cdr.

-----

8 points by pg 6673 days ago | link

I didn't mean Arc will never have cdar. But to avoid having a language designed according to arbitrary rules rather than the actual demands of the task, I've been trying to be disciplined about not adding operators till I need them.

-----

5 points by parenthesis 6673 days ago | link

I suppose you can cdr:car .

On the one hand, it does feel like all the c....r s should be there.

On the other hand, I think cadr is pretty much the only one I ever actually use; and it is there.

-----

2 points by drcode 6673 days ago | link

I noticed this contradiction, too... :) If we're not going to use c[ad]r composability, why not just use unique, easily distinguishable names for all of these that don't compose:

  car  --> hd
  cdr  --> tl
  caar --> inner
  cddr --> skip2
  cadr --> second
...or something like that. Unique names would reduce errors.

-----

4 points by soegaard 6674 days ago | link

Never mind the example. What troubles me with the the-code-is-the-spec approach, is that for an outsider, it is impossible to tell which decisions where made deliberately and which were accidental.

Just for the record, I find it is fair game to say there is no specification, while the experimentation phase is still going on.

-----

5 points by pg 6674 days ago | link

It doesn't matter whether features are deliberate or not. It's very common in math for someone to discover something that has interesting properties they never imagined. In fact, it's probably closer to the truth to say that if a mathematical concept doesn't have properties the discoverer never imagined, it's not a very interesting one.

Lisp itself is an example of this phenomenon. JMC didn't expect to use s-expressions in the real language, but they turned out to be way more useful than he envisioned.

I'm not just splitting hairs here, or trying to defend myself. In design (or math), questions of deliberateness are not binary. I'll often decide on the design of an operator based on what looks elegant in the source, rather than to meet some spec, just as Kelly Johnson used beauty as a heuristic in designing aircraft that flew well.

-----

1 point by shiro 6673 days ago | link

It's a good argument in general sense, but I doubt it is applicable in library API.

If you're delivering a final product, users don't care if some design is deliberate or not; they care it is good or bad. If you're deriving mathematic proof, others don't care if some choice is deliberate or not; they care if it is correct, beautiful, or useful to prove other theorems. That's because changing your choice afterwards won't break existing products or proofs that relied on the previous choices.

In the case of library API, changing your choice does break existing software relying on the library. In the current Arc case it is forewarned so it's perfectly fine, but at some point (50years from now, maybe?) you have to let more people write software on it; by that moment it should be clear that what they can rely on and what they cannot.

-----

2 points by pg 6673 days ago | link

by that moment it should be clear that what they can rely on and what they cannot

The only difference if the implementation is the spec is how they know what they can rely on. If the implementation is the spec, they decide by reading the source; if it's a document writen in English, they decide by reading that.

-----

4 points by shiro 6673 days ago | link

Implementation can be, and will be, changed, inevitably. Then does the language change as well, or the languages remains the same but just implementation is improved? How can you tell the difference purely from the source?

Some Scheme implementation evaluates arguments left to right. You can see that by reading the source. In future, it may switch it right to left, maybe for better optimization. The spec in natural language, or more formal and abstract form like in Appendix A of R6RS, can explicitly say the order of evaluation is unspecified. How you tell your users that they should not rely on the evaluation order purely by the source code, given the state of most programming languages?

Ideally I like to think the source only describes the spec and the compiler and runtime figure out the details, so maybe spec-by-source and spec-by-other-notation will converge in future. Is that what you are talking?

(Please don't argue that unspecified evaluation order is bad or not; I just use that example to illustrate the point. And incidentally, since Arc is defined in terms of Scheme, the order of argument evaluation order is just unspecified as well. But it's just delegating the spec to a lower level.)

-----

1 point by soegaard 6673 days ago | link

Actually PLT Scheme guarantees left-to-right order, but that doesn't change your point.

-----

2 points by akkartik 6673 days ago | link

One point everybody else is missing: since arc explicitly makes no claims of backwards compatibility, the notion of a spec is meaningless.

If the goal of a language is to be readable there's nothing wrong in the implementation being the spec. Consider it a form of self-hosting, or of eating your own dogfood.

---

An implementation in a reasonably high-level declarative language is a more reasonable 'spec' than one in C. More features are amenable to checking just by reading rather than by execution.

When something is obviously a bug it doesn't matter if it's a bug in the spec or the implementation.

Those two categories -- obvious bugs, and questions about what is or is not in the language that should be answered by reading the code rather than executing it -- seem to cover all the objections people have raised.

-----

1 point by shiro 6673 days ago | link

At least I'm talking about the attitude of spec-by-source in general, not particularly about Arc, FYI.

Edit: I agree that more abstract, declarative language is closer to spec-by-source. If somebody says Prelude.hs is the spec of Haskell's standard library I agree. But the core language semantics is still not in Haskell itself, is it? (I'm still learning. Correct me if I'm wrong.)

-----

1 point by almkglor 6673 days ago | link

Right. And nobody needs comments in source code, ever.

-----

1 point by akkartik 6673 days ago | link

What!?

FWIW, here's how I think comments in source code can be improved, especially in exploratory programming: http://akkartik.name/codelog.html

-----

1 point by almkglor 6673 days ago | link

Oh come on. What are comments but prose descriptions of the code?

Anyway please reparse my post with <sarcastic>...</sarcastic> around it.

-----

1 point by akkartik 6673 days ago | link

I did get the sarcasm, but not how you extrapolate from my comment.

"There's nothing wrong with a code spec in this case" != "prose specs suck"

-----

1 point by almkglor 6673 days ago | link

Hmm. Must be a bug in my reader ^^

-----

2 points by oconnor0 6673 days ago | link

The problem is that as people learn the language they will build mental maps of what works and what doesn't and in the process will write code that depends on things that could legitimately be considered bugs or arbitrary side effects of the current implementation.

Whether or not this matters to you or even should matter is another concern, but this has been a spot of contention for languages like Python and OCaml whose spec is the code.

-----


Damn, it must be all the Fibonacci calculations that are making News.YC so unusably slow...

Seriously, though, if you're curious about why the Arc version is slower, just look at the Scheme code that the function ac produces as its output. Maybe there's some obvious optimization we're missing.

-----

9 points by kens 6674 days ago | link

I thought from looking at the internals before that the ar_funcall overhead would be the main factor. However, it turns out that of the 40 seconds, about 25 are spent in _<, _+ takes about 9 seconds, ar-funcalls about 2.5 seconds, ar-false? and _- about 1 second each.

So it looks like arc< is the main time sink, although the others all contribute. I think checking the type of all the arguments is costly.

In case anyone is interested, the Scheme code assigned to _fib is:

  (lambda (n)
    (quote nil)
    (if (not (ar-false? (ar-funcall2 _< n 2))) 1
      (ar-funcall2 _+ (ar-funcall1 _fib (ar-funcall2 _- n 1))
                      (ar-funcall1 _fib (ar-funcall2 _- n 2)))))

-----

10 points by shiro 6674 days ago | link

I don't know much about MzScheme internals. But from my experience of implementing Gauche Scheme, inlining basic operators such as <, +, and - is a big performance boost, and I suspect MzScheme does similar thing as far as these ops are not redefined. Calling them many times via 'wrapper' function like ac.scm does diminishes that effect.

That said, one possible overhead is that, although arc compiler tries to use ar-funcall2 to avoid consing the arguments, they are consed after all since _+ and _< are defined to take a list of arguments.

The original (time (fib 34)) took 79.8s on my machine.

Changing arc< in ac.scm with this:

    (define arc<
      (case-lambda
        [(a b)
         (cond [(and (number? a) (number? b)) (< a b)]
               [(and (string? a) (string? b)) (string<? a b)]
               [(and (symbol? a) (symbol? b)) (string<? (symbol->string a)
                                                        (symbol->string b))]
               [(and (char? a) (char? b)) (char<? a b)]
               [else (< a b)])]
        [args
         (cond ((all number? args) (apply < args))
               ((all string? args) (pairwise string<? args #f))
               ((all symbol? args) (pairwise (lambda (x y)
                                               (string<? (symbol->string x) 
                                                         (symbol->string y)))
                                             args
                                             #f))
               ((all char?   args) (pairwise char<?   args #f))
               (#t                 (apply < args)))]))
made (time (fib 34)) 72.8s, and further changing _+ with this:

    (xdef '+
          (case-lambda
            [() 0]
            [(a b)
             (cond [(and (string? a) (string? b)) (string-append a b)]
                   [(and (arc-list? a) (arc-list? b))
                    (ac-niltree (append (ar-nil-terminate a)
                                        (ar-nil-terminate b)))]
                   [else (+ a b)])]
            [args
             (cond [(all string? args) 
                    (apply string-append args)]
                   [(all arc-list? args) 
                    (ac-niltree (apply append (map ar-nil-terminate args)))]
                   [else (apply + args)])]))
made (time (fib 34)) 49.5s.

But generally, these kind of tuning for microbenchmarks only have small effect in real applications, for microbenchmarks magnifies inefficiency of very specific parts.

(Afterthought: It won't be too difficult to write a Scheme macro that expands variable-arity type-dispatching functions like _+ or _+ into case-lambda form. Then this kind of optimization can be applied without cluttering the source too much.)

-----

2 points by pg 6675 days ago | link | parent | on: Binary Search Trees in Arc

Are you sure it works the same? Bst-rem-edge is simpler than bst-rem; it doesn't rebalance the tree.

-----

2 points by rkts 6675 days ago | link

Maybe I'm not understanding your code (it's kind of cryptic...) but it does seem the same to me. When bst-rem removes a node with one child, it just grabs the other child. This is the same thing bst-rem-edge does.

Your 'bubble' function calls bst-edge to find the left/rightmost node and then calls bst-rem-edge to remove it. You could just as well call bst-edge and then bst-rem on the result; either way you traverse the tree twice. I can't imagine a situation where you'd just want bst-rem-edge by itself.

Come to think of it, I don't know why you need bst-edge either. It's simpler to just have min and max functions that return an element, as in the Haskell solution.

-----

More