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

Re: Does my ideal language exist?



In article <m3vgyxy2se.fsf@localhost.localdomain>, Lieven Marchand 
<mal@bewoner.dma.be> wrote:

> Bruce Hoult <bruce@hoult.org> writes:
> 
> > In article <m3d7l6x9fe.fsf@localhost.localdomain>, Lieven Marchand 
> > <mal@bewoner.dma.be> wrote:
> > 
> > > Bruce Hoult <bruce@hoult.org> writes:
> > > 
> > > > So it's not the infix syntax per se that you object to?  Or, at 
> > > > least, 
> > > > it's hard to see how it could be if you don't like R5RS macros 
> > > > either.
> > > 
> > > No. I want an extensible language that allows me to design my own
> > > abstractions and expressing them in the most natural way. In order to
> > > have that in a sane way you need a syntax that doesn't get in the
> > > way.
> > 
> > Surely Scheme has that?
> > 
> 
> Not really if you go by strict R^nRS. They've thrown out the concept
> of a reader and defined a program as a text string instead of as a
> list.

So, to you, Scheme is no longer a Lisp?  (or never was?)  Fair enough, 
but clearly our most basic terms differ.  I regard CL, Scheme, emacs 
lisp, and Dylan as all being Lisps.  Hell, Perl is damn near a Lisp 
these days.  They all have the lambda nature.


> > > I have nothing against infix per se but I have never seen a useful
> > > extensible language with infix syntax.
> > 
> > I happen to think that Dylan is.
> 
> Take a look at a recent thread in comp.lang.dylan named 'Crash course
> on macros?'. Something as conceptually simple as a COLLECTING macro
> turns into a for me completely unreadable mess of more than a page.
> 
> (defmacro with-collect ((&rest collectors) &body forms)
>   "Evaluate forms, collecting objects into lists.
> Within the FORMS, you can use local macros listed among collectors,
> they are returned as multiple values.
> E.g., (with-collect (c1 c2) (dotimes (i 10) (if (oddp i) (c1 i) (c2 i))))
>  ==> (1 3 5 7 9); (0 2 4 6 8) [2 values]"
>   (let ((ret (mapcar (lambda (cc) (gensym (format nil "~s-RET-" cc)))
>                      collectors)))
>     `(let (,@ret)
>       (macrolet ,(mapcar (lambda (co re) `(,co (form) `(push ,form 
>       ,',re)))
>                          collectors ret)        ,@forms        (values 
>                          ,@(mapcar (lambda (re) `(nreverse ,re)) 
>                          ret))))))
> 
> And that's for a simple thing.

The problem is that the "completely unreadable mess of more than a page" 
provided a *lot* more functionality than your little macro above.

See what you think about the following.  Exact same functionality as 
yours, and I don't think it's any harder to understand, and perhaps 
easier.  It depends on what you're used to, I guess, but I don't have 
any trouble following either (or writing the Dylan one, of course :-)  
Even the length is about the same -- yours is nine lines as presented, 
mine is eight.  Obviously yours could be reformatted to use fewer (and 
so could mine), or either could be perhaps made more understandable by 
using a few more lines.  I'm willing to call it a draw.

-----------------------------------------------------------------
module: collect
synopsis: make a "collect" macro corresponding to a given Lisp one
author: Bruce Hoult

define macro with-collect
  { with-collect (?:name, ?more-names:*) ?:body end }
    => {let vals = #();
        local method ?name(item) vals := pair(item, vals) end;
        let (#rest results) = collect (?more-names) ?body end;
        apply(values, reverse!(vals), results);}
  { with-collect () ?:body end } => { ?body; values() }
end;

define function dotest()
  with-collect (c1, c2)
    for (i from 0 below 10)
      if (odd?(i)) c1(i) else c2(i) end;
    end;
  end;
end dotest;

define function main()
  let (odd, even) = dotest();
  format-out("odd = %=, even = %=\n", odd, even);
end function main;

main();
-----------------------------------------------------------------
[bruce@k7 collect]$ time make;./collect 
[... snip ...]
Compilation finished with 0 Warnings and 0 Errors
8.65user 0.75system 0:10.14elapsed 92%CPU (0avgtext+0avgdata 
0maxresident)k
0inputs+0outputs (9495major+12263minor)pagefaults 0swaps
odd = #(1, 3, 5, 7, 9), even = #(0, 2, 4, 6, 8)
-----------------------------------------------------------------

That was with d2c, from www.gwydiondylan.org.

For your amusement, here is the intermediate C code for the entire 
"dotest()" function.  I've taken out only whitespace (incl comments).  
The multiple value return code could stand some work, but I think you'll 
find it's not too bad as is:

-----------------------------------------------------------------
descriptor_t * collectZcollectZdotest_FUN(descriptor_t *orig_sp)
{
    descriptor_t *cluster_0_top;
    descriptor_t *cluster_1_top;
    heapptr_t L_vals; /* vals */
    heapptr_t L_vals_2; /* vals */
    long L_i; /* i */
    long L_i_2; /* i */
    descriptor_t L_temp;
    descriptor_t L_temp_2;
    heapptr_t L_instance; /* instance */
    descriptor_t L_temp_3;
    descriptor_t L_temp_4;
    heapptr_t L_instance_2; /* instance */
    heapptr_t L_arg1; /* arg1 */
    descriptor_t L_temp_5;
    heapptr_t L_results; /* results */
    heapptr_t L_arg1_2; /* arg1 */
    descriptor_t L_temp_6;

    L_vals = dylanZempty_list.heapptr;
    L_vals_2 = dylanZempty_list.heapptr;
    L_i = 0;
    while (1) {
        L_i_2 = L_i;
        if ((L_i_2 < 10)) {
            if (((L_i_2 & 1) == 0)) {
                L_temp.heapptr = collectZliteral.heapptr;
                L_temp.dataword.l = L_i_2;
                L_temp_2.heapptr = L_vals_2;
                L_temp_2.dataword.l = 0;
                L_instance = dylanZdylan_visceraZCLS_pair_MAKER_FUN
                   (orig_sp, L_temp, L_temp_2);
                L_vals_2 = L_instance;
            }
            else {
                L_temp_3.heapptr = collectZliteral.heapptr;
                L_temp_3.dataword.l = L_i_2;
                L_temp_4.heapptr = L_vals;
                L_temp_4.dataword.l = 0;
                L_instance_2 = dylanZdylan_visceraZCLS_pair_MAKER_FUN
                   (orig_sp, L_temp_3, L_temp_4);
                L_vals = L_instance_2;
            }
            L_i = (L_i_2 + 1);
        }
        else {
            goto block0;
        }
    }
  block0:;
    L_arg1 = dylanZdylan_visceraZreverseD_METH
       (orig_sp, L_vals_2, dylanZliteral_67.heapptr);
    L_temp_5.heapptr = L_arg1;
    L_temp_5.dataword.l = 0;
    cluster_0_top = orig_sp + 1;
    orig_sp[0] = L_temp_5;
    cluster_1_top = values_sequence
       (cluster_0_top, dylanZliteral_7.heapptr);
    cluster_0_top = cluster_1_top;
    L_results = dylanZdylan_visceraZmake_rest_arg_FUN
       (cluster_0_top, orig_sp + 0, cluster_0_top - orig_sp - 0);
    L_arg1_2 = dylanZdylan_visceraZreverseD_METH
       (orig_sp, L_vals, dylanZliteral_67.heapptr);
    L_temp_6.heapptr = L_arg1_2;
    L_temp_6.dataword.l = 0;
    cluster_0_top = orig_sp + 1;
    orig_sp[0] = L_temp_6;
    cluster_1_top = values_sequence(cluster_0_top, L_results);
    cluster_0_top = cluster_1_top;
    return cluster_0_top;
}
-----------------------------------------------------------------


> CLOS has historically been implemented as a macro system without any
> support from the underlying Lisp. Could I build something like that
> with Dylan macros?

I honestly don't know, as I haven't seen the ones implementing CLOS.


> > CL and Scheme use () for every imaginable kind of grouping.  Infix 
> > languages tend to have different grouping tokens for function 
> > arguments, 
> > vector elements, sequential blocks of code, and control constructs.  
> > This helps to reinforce the information gained form the other main 
> > grouping cue: indentation.
> > 
> 
> Experienced Lispers don't even see (). They're for the
> editor. Indentation isn't only a cue in Lisp, it's everything. That's
> why there in in practice one way to indent Lisp source and anyone who
> doesn't follow that will be forcibly corrected by his colleagues.

Well, you can say that but it plainly isn't true.  I mean, look at the 
code you posted, with the () removed:

   with-collect c1 c2 dotimes i 10 if oddp i c1 i c2 i

What does the indentation tell us here?  Not a lot :-)  Is it possible 
to parse it?  Yes, as Logo and FORTH have proved.  But it's not very 
pleasant.


> > The second advantage is the context gained by interspersing unique 
> > "noise" tokens into things.  For example, writing "for (i from 10 to 
> > 100 
> > by 3)" instead of (do ((i 10) (+ i 3)) (< i 100) ... ).  This is 
> > actually one of the best things about Smalltalk syntax, that I wish 
> > would be adopted by more languages.
> 
> So, build it with a macro ;-) Or in this case, use the macro defined
> for you by the nice ANSI CL committee:
> 
> (loop for i from 10 to 100 by 3 summing i)

But that's *exactly* what Dylan *does*!!!

When I write "for (i from 10 to 100 by 3) ... end" in Dylan it is 
actually transformed by a macro in the standard library into:

let init = 10;
let limit = 100;
let by = 3;
local method repeat(i)
   block (return)
      unless (i > limit)
         ...;
         return(repeat(i + by));
      end;
   end;
end;
repeat(init);

The "block" and "unless" are further transformed by other macros before 
you get down to the core Dylan language that the compiler proper 
actually works with.


> My experience with Lisp is different and I find Dylan hard to read. I
> don't like the <> convention and a lot of the other Scheme like stuff
> like ? etc.

There are always matters of taste.  I don't much like <> myself and 
often don't do that in my own code.  And I like the $var$ convention for 
globals even less.  If you were fanatical about it, you can always 
rename all the system library stuff on import to something without the 
<> around the class names.

But, really, isn't this once again a pretty trivial level to base a like 
or dislike for a language upon?  If you couldn't do your with-collect 
macro in a reasonable way then that would certainly be something to 
complain about, but minor naming conventions???

-- Bruce



Follow-Ups: