Arc Forumnew | comments | leaders | submitlogin
2 points by fallintothis 5332 days ago | link | parent

Shouldn't 'forlen be used instead in your examples?

I'd say each should be used in the examples. The point is that they are easy instances of a more general problem, as I noted: when using for, you're typically using expressions; when you're using expressions, you aren't sure if the bounds will result in an ascending or descending loop. There are instances where this distinction is important. Take posmatch in strings.arc, defined as

  (def posmatch (pat seq (o start 0))
    (catch
      (if (isa pat 'fn)
          (for i start (- (len seq) 1)
            (when (pat (seq i)) (throw i)))
          (for i start (- (len seq) (len pat))
            (when (headmatch pat seq i) (throw i))))
      nil))
Here we see the else-clause for-loop isn't merely a place to substitute forlen or each: it only iterates up (by for's behavior) to the largest index at which the pattern could occur in the sequence:

  arc> (load "trace.arc") ; see http://arclanguage.org/item?id=10372
  nil
  arc> (trace posmatch headmatch)
  *** tracing posmatch
  *** tracing headmatch
  nil
  arc> (= trace-indent* 2)
  2
  arc> (posmatch "a" "abc")
  1. Trace: (posmatch "a" "abc")
    2. Trace: (headmatch "a" "abc" 0)
    2. Trace: headmatch ==> t
  1. Trace: posmatch ==> 0
  0
  arc> (posmatch "abc" "a")
  1. Trace: (posmatch "abc" "a")
  1. Trace: posmatch ==> nil
  nil
As a drop-in replacement, the bidirectional variant would fail:

  arc> (untrace posmatch)
  *** untracing posmatch
  nil
  arc> (mac for_ (v init end . body)
         (w/uniq (gv gi ge gt gf)
           `(do
              (if (> ,end ,init)
                (= ,gt < ,gf +)   ; classic, "ascendant", 'for
                (= ,gt > ,gf -))  ; 'down
              (with (,gv nil ,gi ,init ,ge (,gf ,end 1))
                (loop (assign ,gv ,gi) (,gt ,gv ,ge) (assign ,gv (,gf ,gv 1))
                  ((fn (,v) ,@body) ,gv))))))
  #(tagged mac #<procedure: for_>)
  arc> (def posmatch (pat seq (o start 0))
         (catch
           (if (isa pat 'fn)
               (for_ i start (- (len seq) 1)
                 (when (pat (seq i)) (throw i)))
               (for_ i start (- (len seq) (len pat))
                 (when (headmatch pat seq i) (throw i))))
           nil))
  *** redefining posmatch
  #<procedure:zz>
  arc> (trace posmatch)
  *** tracing posmatch
  nil
  arc> (posmatch "a" "abc")
  1. Trace: (posmatch "a" "abc")
    2. Trace: (headmatch "a" "abc" 0)
    2. Trace: headmatch ==> t
  1. Trace: posmatch ==> 0
  0
  arc> (posmatch "abc" "a")
  1. Trace: (posmatch "abc" "a")
    2. Trace: (headmatch "abc" "a" 0)
  Error: "string-ref: index 1 out of range for empty string"
For strange behaviours where bounds would be inversed, well, I always check the input where needed (general rule).

Your point, if I understand it, is that you'd rather catch this behavior in the logic of the code, e.g.,

  (if (and (>= (len seq) (len pat))
           (<= start (- (len seq) (len pat))))
    (for_ i start (- (len seq) (len pat))
      ...))
That's fine, of course. It works. It just seems gratuitous -- like something I'd want handled for me already. But everyone has their definitions of "good", and yours is certainly no less (or more) valid than mine.

Hell, someone might like having the bidirectional loop in general, then use a separate loop ("up"?) for this case.

The programmer should have the easy life (i.e: not having to remember 100 loop constructs)

Whereas I think remembering 100 loop constructs is easier than remembering that the handful of loop constructs are incredibly fragile.

But of course, if you like 'for to be like this, I see no problem with this.

Nor do I see a problem if you want a bidirectional for. This is one use for macros: rather than worry that Arc doesn't have some loop construct, you're allowed to make your own. No need for the language spec to get updated if you can easily write a bidirectional loop. And if for was changed to be bidirectional, I could similarly write macros for ascending and descending loops.

As you say, this is just the rationale. But that's not saying much: by its very nature, language design is about rationale; the only "necessary" components of the language are basically the ones that make it Turing-complete.