Does my ideal language exist? 
Author Message
 Does my ideal language exist?


Quote:





> > > > 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.

- Show quoted text -

Quote:
> > > 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)))

>       (macrolet ,(mapcar (lambda (co re) `(,co (form) `(push ,form
>       ,',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();
-----------------------------------------------------------------

[... 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 amu{*filter*}t, 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;

Quote:
}

-----------------------------------------------------------------

Quote:
> 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.

Quote:
> > 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.

Quote:
> > 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.

Quote:
> 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 ...

read more »



Fri, 13 Dec 2002 03:00:00 GMT  
 Does my ideal language exist?

Quote:

> 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;

Arrrgghh.  I really should learn not to do last-minute edits before
posting.  My macro was originally called "collect" and I edited it to
match yours at the last minute.  But not *quite*...

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) = with-collect (?more-names) ?body end;
        apply(values, reverse!(vals), results);}
  { with-collect () ?:body end } => { ?body; values() }
end;

-- Bruce



Fri, 13 Dec 2002 03:00:00 GMT  
 Does my ideal language exist?

Quote:

> 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) = with-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;

Actually, one thing confuses me here.

I can instead write the base case as...

   { with-collect () ?:body end } => { ?body }

... and it works just fine.  The downside is that (as you'd expect) you
get a third, spurious result returned from dotest(), namely the result
of the body which, being a for() with no explicit return value, is #f.

The confusing part is that this way generates quite a bit better code
for the reverse!()'s and the multiple value return -- the two calls to
values_sequence() and the call to make_rest_arg_FUN() are completely
gone!

This remains true even if I add several more collectors to dotest():

define function dotest()
  with-collect (c1, c2, c3, c4)
    for (i from 0 below 10)
      if (odd?(i)) c1(i) else c2(i) end;
      c3(i + 7);
      c4(i * 10);
    end;
    123456;
  end;
end dotest;

The function epilog is now:

    L_arg1 = dylanZdylan_visceraZreverseD_METH
       (orig_sp, L_vals_4, dylanZliteral_67.heapptr);
    L_arg1_2 = dylanZdylan_visceraZreverseD_METH
       (orig_sp, L_vals_3, dylanZliteral_67.heapptr);
    L_arg1_3 = dylanZdylan_visceraZreverseD_METH
       (orig_sp, L_vals_2, dylanZliteral_67.heapptr);
    L_arg1_4 = dylanZdylan_visceraZreverseD_METH
       (orig_sp, L_vals, dylanZliteral_67.heapptr);
    L_temp_9.heapptr = L_arg1_4;
    L_temp_9.dataword.l = 0;
    orig_sp[0] = L_temp_9;
    L_temp_10.heapptr = L_arg1_3;
    L_temp_10.dataword.l = 0;
    orig_sp[1] = L_temp_10;
    L_temp_11.heapptr = L_arg1_2;
    L_temp_11.dataword.l = 0;
    orig_sp[2] = L_temp_11;
    L_temp_12.heapptr = L_arg1;
    L_temp_12.dataword.l = 0;
    orig_sp[3] = L_temp_12;
    L_temp_13.heapptr = collectZliteral.heapptr;
    L_temp_13.dataword.l = 123456;
    orig_sp[4] = L_temp_13;
    return orig_sp + 5;

Does anyone understand this?  Changing the original values() to read
values(123456) or values("sex") also produces the good code above, but
an empty values() causes really bad code.

What is it that the empty values() is doing?  Surely a bug?

-- Bruce



Fri, 13 Dec 2002 03:00:00 GMT  
 Does my ideal language exist?

Quote:
> What is it that the empty values() is doing?  Surely a bug?

Perhaps a conscious decision by the compiler writer to optimize single-value
return because it is the most common, and treat 0 values as just another
instance of multiple-value return.


Fri, 13 Dec 2002 03:00:00 GMT  
 Does my ideal language exist?

Quote:


> > What is it that the empty values() is doing?  Surely a bug?

> Perhaps a conscious decision by the compiler writer to optimize
> single-value
> return because it is the most common, and treat 0 values as just another
> instance of multiple-value return.

No, because if I put more than one value in the values() call then it
generates the good code.  And functions => () work fine in most contexts.

I currently think it's a confusion bwteeen returning exactly zero values
and returning an unspecified number of values (called a "wild-ctype"
inside d2c).  Usually this works fine as there is machinery to
distinguish them, but maybe not in the "apply" transformer in
optimize/trans.dylan, or possibly in the "merge-clusters" transformer in
the same place.  It turns out that if I replace the call to values()
with a call to foo(), where foo() is defined as...

   define method foo() => ();
   end

... then the same thing happens.

-- Bruce



Sat, 14 Dec 2002 03:00:00 GMT  
 Does my ideal language exist?

Quote:

> So, to you, Scheme is no longer a Lisp?  (or never was?)  Fair enough,

Not really. It's an algol-like language with prefix syntax.

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

I couldn't work out all the details of what it was doing.

Quote:

> > 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.

I think you would miss SYMBOL-MACROLET to implement with-slots. You
need to cause a given symbol to expand to something in any context.

Quote:
> 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.

That came out of the doc-string where it was folded for brevity. In
normal cod e it would have been indented in the standard way.

Quote:
> 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???

It will boil down to matters of taste probably. I would prefer Dylan
above C, C++ or Perl any day.

--

When C++ is your hammer, everything looks like a thumb.      Steven M. Haflich



Sat, 14 Dec 2002 03:00:00 GMT  
 Does my ideal language exist?

Quote:

> As for "multiple left hand arguments", There really is not
> reason for it in many languages. C for example - just return
> a structure or pass the return values as pointers to the
> function.

Neither is a good solution.

Multiple return values shouldn't be put into a structure any more than
should function arguments.  They are independent values, with no
relationship between them.  Using a structure in e.g. C implies a memory
layout, it implies addreses, it implies in struct {int16 a; int16 b}
that &foo.b is greater than &foo.a by an amount of sizeof(int16) and the
compiler can never be sure that the programmer won't do something like
int32 x = *(int*)&foo.a.

Passing in pointers to the places to return values is no better.  OK,
they are explcitly different values now, but they also now are
explicitly in memory, not in registers.

The correct solution is to use exactly the same convention to pass back
multiple return values as is used to pass in multiple function
arguments.  i.e. in C an appropriate translation of the Dylan...

  define method sincos(x :: <single-float>, y :: <single-foat>)
   => (sin :: <single-float>, cos :: <single-float>);
     let hyp = sqrt(x * x = y * y);
     values( y / hyp, x / hyp );
  end;

  define method foo();
    let a = 3.0;
    let b = 4.0;
    let (s, c) = sincos(a, b);
    format-out("sin is %=, cos is %=\n", s, c);
  end;

... would be ...

  void sincos(float x, float y, void (*results)(float, float)){
    float hyp = sqrt(x*x+y*y);
    results(y/hyp, x/hyp);
  }

  void foo(){
    float a = 3.0;
    float b = 4.0;
    sincos(a, b, foo_1);
  }

  void foo_1(float s, float c){
     printf("sin is %f, cos is %f\n", s, c);
  }

This will compile into machine code like this (on a typical RISC,
passing args in registers and keeping the return address in a register):

sincos:
   push ret ;save return address
   mul t1 = arg1*arg1
   mul t2 = arg2*arg2
   add hyp = t1 + t2
   mov t3 = arg1
   div arg1 = arg2/hyp
   div arg2 = t3/hyp
   call (arg3)
   pop ret
   jmp (ret)

foo:
   push ret
   load arg1 = 3.0
   load arg2 = 4.0
   load arg3 = foo_1
   call sincos
   pop ret
   jmp (ret)

foo_1:
   push ret
   mov arg3 = arg2
   mov arg2 = arg1
   load arg1 = str_1
   call printf
   pop ret
   jpm (ret)

str_1:
   .string "sin is %f, cos is %f\n"

Not *too* bad.  But given a compiler which optomises tail-calls (not
many C compilers :-(), it will compile into machine code like this:

sincos:
   mul t1 = arg1*arg1
   mul t2 = arg2*arg2
   add hyp = t1 + t2
   mov t3 = arg1
   div arg1 = arg2/hyp
   div arg2 = t3/hyp
   jmp (arg3)

foo:
   load arg1 = 3.0
   load arg2 = 4.0
   load arg3 = foo_1
   jmp sincos ;sincos will return to our caller (via foo_1 and printf)

foo_1:
   mov arg3 = arg2
   mov arg2 = arg1
   load arg1 = str_1
   jmp printf ;printf will return to our caller

str_1:
   .string "sin is %f, cos is %f\n"

What did this take?  Nothing more than noticing that if there is the
sequence "call foo;pop ret;jmp (ret)" then you can replace it with
simply "jmp foo" and rmove the "push ret" at the start of the function
as well.

- 15 instructions instead of 24
- all the function calls have turned into gotos
- we don't push/pop the stack at *all*
- the multiple return values are passed in registers
- there are no structs, pointers, or anything that touches memory

The source code looks unnecessarily messy in C, because C doesn't have
nested functions.  It's OK here because a and b aren't needed after the
sincos() call.  If they were then Pascal is slightly cleaner:

  procedure sincos(x : real, y : real, results : procedure(real,real))
  var
    hyp : real;
  begin
    hyp := sqrt(x*x+y*y);
    results(y/hyp, x/hyp);
  end;

  procedure foo
  var
     a, b : real;

     procedure foo_1(s : real, c : real)
     begin
        writeln('for a=', a, ', b=', b, ', sin is ', s, ', cos is ', c);
     end;
  begin
     a := 3.0;
     b := 4.0;
     sincos(a, b, foo_1);
  end;

Better still is a language in which nested functions can be declared
inline at the point of use -- preferably anonymously.  Common Lisp,
Scheme, Dylan, Perl, PL/I all qualify.

Best is just to have multiple results in the language in the first place
so that you don't have to do this exlicit CPS stuff yourself...

Quote:
> Technically speaking:

> while (1) {}

> is the same as:

> myLabel:
> goto myLabel;

True.

Quote:
> with some differences:

> 1. The while must execute a conditional if, more time

Not true.  Take a look at the output of any recent compiler.

Quote:
> 2. 99% of the time, the while() makes use of an outside
> variable, more memory usage

Not true.

Quote:
> 3. the while() is syntaxically(?) better

Yes, because of the {}.  But ...

loop
   ...
end

... or ...

loop {
   ...

Quote:
}

... is even better.

Quote:
> But there are times when goto's are very appropriate, just
> the fact that assembler still has absolute jumps means that
> they haven't outlived their usefulness.

I disagree.  Given lexical nesting of functions and a compiler that
optimizes tail-calls there is never any need for goto.

-- Bruce



Sun, 15 Dec 2002 03:00:00 GMT  
 Does my ideal language exist?
Once you get used to returning multiple values, return structs look very
awkward. I converted a toy XML parser from JavaScript to Dylan a while back,
and the JavaScript version was built around some sort of current data /
remaining data struct that really obfuscated the code. In Dylan, each
function could just return the values required, and the code made a lot more
sense and wasted less memory.

Out-parameter pointers are one of the main causes of const. const looks like
a good idea, but it's a side effect of that broken paradigm and a real pain
to maintain.

On the subject of goto, Apple's C sample code tends to use goto blocks as
finally: clauses in functions. This works quite well, and avoids deeply
nested ifs simply for error handling.

- Rob.

--

Quote:

> Organization: The Internet Group Ltd
> Date: Tue, 27 Jun 2000 20:30:01 -0400 (EDT)

> Subject: Re: Does my ideal language exist?



>> As for "multiple left hand arguments", There really is not
>> reason for it in many languages. C for example - just return
>> a structure or pass the return values as pointers to the
>> function.

> Neither is a good solution.



Sun, 15 Dec 2002 03:00:00 GMT  
 
 [ 8 post ] 

 Relevant Pages 

1. Does my ideal language exist?

2. Finding Bugs(was: Does my ideal language exist)

3. Does my ideal language exist?

4. Does my ideal language exist?

5. Does my ideal language exist?

6. Does my ideal language exist?

7. What's so ideal about Ideal?

8. Ideal language and bad cpu design.

9. Operator overloading [was: Re: Does my ideal language exist?]

10. How much assembly language programming is done?

11. Doe the language have an standard?

12. Event based language, does it exist?

 

 
Powered by phpBB® Forum Software