[Prev][Next][Index][Thread]
Re: Does my ideal language exist?
-
To: info-dylan@ai.mit.edu
-
Subject: Re: Does my ideal language exist?
-
From: Bruce Hoult <bruce@hoult.org>
-
Date: Tue, 27 Jun 2000 20:30:01 -0400 (EDT)
-
Organization: The Internet Group Ltd
-
References: <m34s82daqz.fsf@enterprise.newedgeconcept> <m3n1k97bte.fsf@localhost.localdomain> <8j8dqu$2ass@odds.stat.purdue.edu> <slrn8lglgo.5fv.qrczak@qrnik.knm.org.pl> <8jaq33$1c7g@odds.stat.purdue.edu> <qDb65.392$o7.116882@news.uswest.net>
-
User-Agent: MT-NewsWatcher/3.0 (PPC)
-
Xref: traf.lcs.mit.edu comp.lang.misc:33329 comp.lang.forth:57252 comp.lang.clos:5022 comp.lang.dylan:12243
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: