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

Re: Does my ideal language exist?



In article <qDb65.392$o7.116882@news.uswest.net>, "Jeff Massung" 
<jmassung@magpiesystems.com> wrote:

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


> Technically speaking:
> 
> while (1) {}
> 
> is the same as:
> 
> myLabel:
> goto myLabel;

True.


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


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

Not true.

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

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

loop
   ...
end

... or ...

loop {
   ...
}

... is even better.


> 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



Follow-Ups: