[Prev][Next][Index][Thread]

Re: C# is not Dylan (was: Re: C# : The new language from M$)



[Newsgroups: trimmed]

Michael T. Richter <mtr@igs.net> wrote:
> Colin Walters <walters@cis.ohio-state.edu> wrote in message
> > [...]  one thing I really miss
> > from Scheme is the "named let" syntax for function calls.  I
> > think this syntax beats both Common Lisp's `do' and `labels', as
> > well as C's `for (;;) {}' construction, in terms of terseness
> > and clarity.  I suppose it wouldn't be hard to write a `letf'
> > macro or something, though.
> 
> [snip definition]
>
> Unfortunately I find Scheme easily as unreadable as Lisp.  As a
> result, I have no tool available to evaluate the elegance of the
> construct you posted.  Can you do a line-by-line commentary (if you
> have the time) to explain: a) how it works and b) what you find
> powerful in it?

Here it is, in Dylan and in Scheme. (I'll use factorial as the
example).

You're probably familiar with using tail-recursive function calls
rather than explicit looping, like so:

  define function fact(n)
    local method fact-tr(k, acc)
            if (k.zero?)
              acc
            else
              fact-tr(k - 1, k * acc)
            end if;
          end method fact-tr;
    fact-tr(n, 1)
  end function fact;

This has the same meaning as the Scheme

  (define (fact n)
    (letrec ((fact-tr (lambda (k acc)
                        (if (zero? k)
                            acc
                            (fact-tr (- k 1) (* k acc))))))
      (fact-tr n 1)))

Well, the pattern of "define a tail-recursive function, and call
it with some initial values to kick off the loop" is a very common
one, and it's kind of visually bad to place the initializers at the
bottom of the loop.

So the named let syntax was invented:

  (define (fact-2 n)
    (let fact-tr ((k n)           ; This creates a tail-recursive fn fact-tr,
  		  (acc 1))        ; with two params "k" and "acc". It then
      (if (zero? k)               ; evaluates it with the args "n" and 1.
  	  acc
  	  (fact-tr (- k 1) (* k acc)))))

This lets you define the initializers at the same place you define the
loop variables.  The comparable Dylan would look like:

  define function fact(n)
    iterate fact-tr(k = n, acc = 1)
      if (k.zero?)
  	acc
      else
  	fact-tr(k - 1, k * acc)
      end if;
    end iterate;
  end function;

The iterate macro can be found in the common-extensions module of the
common-dylan library.

The big advantage to using this style of iteration is that it's
extremely clear what is changing on each pass through the loop,
because you explicitly pass all the changed state to the next
iteration. Invariants become very easy to identify.

The big disadvantage (which Tim Bradshaw alluded to) is that there's
no requirement that you can advance to the next iteration step at any
old point; you can literally call the advance function anywhere. This
can occasionally obscure the control flow.


Neel



Follow-Ups: References: