Labels

A

ais523

Richard said:
No you don't.

You could do it with while() and break too.

You can actually do it with just while and no other control constructs,
if you introduce a lot of extra variables to keep track of which loops
you're breaking out of.

For instance:

int x = 1;
int f(void);
void g(void);
int main(void) {
while (x) {
if (f()) break;
g();
}
}

becomes

int x = 1;
int f(void);
void g(void);
int main(void) {
int exiting_outer_loop = 0;
while (x && !exiting_outer_loop) {
int running_f = 1;
int running_g = 1;
while (f() && running_f) {
running_g = 0;
running_f = 0;
exiting_outer_loop = 1;
}
while (running_g) {
g();
running_g = 0;
}
}
}

It's not a good idea to write code that way; it's a lot harder to
follow for humans (even if I tried my best to provide meaningful
variable names), and probably for compilers too. Yet it's possible to do
arbitrary control flow using nothing but while() and a bunch of extra
variables. (It's even possible to simulate goto this way, although not
setjmp.)
 
B

BartC

ais523 said:
Richard wrote:

You can actually do it with just while and no other control constructs,
if you introduce a lot of extra variables to keep track of which loops
you're breaking out of.
int exiting_outer_loop = 0;
while (x && !exiting_outer_loop) {
int running_f = 1;
int running_g = 1;
while (f() && running_f) {
running_g = 0;
running_f = 0;
exiting_outer_loop = 1;
}
while (running_g) {
g();
running_g = 0;
}
}

Using if and goto, that becomes:

L1:
if (!x) goto L2; // while x
if (f()) goto L2; // if f() break
g(); // g()
goto L1; // end while
L2:

Or rather, this would correspond to the original while loop (I've no idea
whether your code is equivalent or not). (Some lines can be combined, and
perhaps the loop test can be relocated, but there's no need. And L2 may or
may not need a semicolon on it! Actually, probably not, as this kind of code
will have no {...} blocks, only the function-closing }.)

If you had to choose minimal language features in which to express code,
you'd be crazy to choose anything other than if and goto (expressions are
taken for granted, but even these can be simplified to at most a single
operator each, plus assignment. Function calls however will always need up
to N arguments.).

I'm not even sure how arbitrary gotos can achieved with while statements or
function calls. (And if you are translating another language to this one, it
may have constructs only possible with such jumps.)
 
T

Tim Rentsch

BartC said:
In C, you need at least if-statements (with goto) for control flow
within a function.

Control flow within a function isn't necessary either
(including the &&, ||, and ?: operators). Nice to
have certainly, but not necessary.
 
B

BartC

Tim Rentsch said:
Control flow within a function isn't necessary either
(including the &&, ||, and ?: operators). Nice to
have certainly, but not necessary.

I don't know if people are trying to wind me up here, but, without any
control flow inside a function, how do you manage to run *any* code at all?
Other then just executing a consecutive set of statements in sequence.

Even a Turing machine allows you to move backwards and forwards along a
tape! While all processors I'm familiar with have control flow based around
conditional jumps (ie. 'if' and 'goto').

And in the past whenever I've implemented even the simplest language or vm,
it doesn't 'become live' until the point where I've given it conditional
execution ('if') and the ability to repeat some statements ('goto' at
minimum). Then it can start to do useful work. But it might not even have
functions..
 
A

ais523

BartC said:
I don't know if people are trying to wind me up here, but, without any
control flow inside a function, how do you manage to run *any* code at all?
Other then just executing a consecutive set of statements in sequence.

The only way I can think of offhand involves using indexes into arrays
of function pointers; you do arithmetic to choose a "next function" to
call, then index the array to call one of multiple functions without
any conditionals inovlved. You could create loops using recursion.

This would be massively non-recommended, of course. Perhaps I'll try it
for an IOCCC entry sometime.
 
M

Malcolm McLean

I don't know if people are trying to wind me up here, but, without any
control flow inside a function, how do you manage to run *any* code at all?
Other then just executing a consecutive set of statements in sequence.
Lisp-heads have worked out a whole philosophy of what is needed for minimal
programming. With just the symbols nil and T (lisp for NULL and 1), and
what's called the lambda calculus, basically the ability to call a function
with an arbitrary number of arguments, and a terminating condition for
tail recursion, you can calculate anything. But only if you're a lisp-head.
The rest of us think in terms of jumps.
 
A

ais523

Malcolm said:
Lisp-heads have worked out a whole philosophy of what is needed for minimal
programming. With just the symbols nil and T (lisp for NULL and 1), and
what's called the lambda calculus, basically the ability to call a function
with an arbitrary number of arguments, and a terminating condition for
tail recursion, you can calculate anything. But only if you're a lisp-head.
The rest of us think in terms of jumps.

Lambda calculus relies on the existence of closures, something that
don't exist natively in C. You can fake them using function pointers and
structs, though, and probably well enough to allow function pointers
and structs being enough to do arbitrary flow control.
 
B

BartC

ais523 said:
BartC wrote:

The only way I can think of offhand involves using indexes into arrays
of function pointers; you do arithmetic to choose a "next function" to
call, then index the array to call one of multiple functions without
any conditionals inovlved. You could create loops using recursion.

You're right of course. I've just had a go at something along those lines
(see code below).

The function testfn() is what I'm trying to execute without using any
control flow statements or conditional (?:) expressions. It's a silly loop
that outputs the sequence 0 1 2 3 4 5 8 9 10.

The first step was to convert into the set of 'flat' statements shown in
testfnflat(), although at this point it still uses the if and goto
statements (now also paired as if-goto) that I prefer.

Then, each line is put into its own function, an array of those functions is
created (program[]), and a 'program counter' is set up in pc (an index into
the function array). If-goto pairs have to be transformed.

'Control flow' inside this 'function' (since this is now disseminated
throughout the file, and any local variables have become globals), is done
by manipulating the value of pc through arithmetic.

The function dispatch loop was turned into recursive calls, relying on an
exit(0) in the code to execution (I suppose a stop flag is simple enough
too).

It worked. Although it is more now of a little virtual machine to execute
the code, rather than executing it directly.

#include <stdio.h>
#include <stdlib.h>

void testfn (void)
{int i;

i=0;
while (1) {
printf("%d ",i);
if (i==10) break;
if (i==5)
i=i+3;
else
++i;
}
}

void testfnflat (void)
{int i;

i=0; // line 1
L1: // line 2
printf("%d ",i); // line 3
if (i==10) goto L2; // line 4
if (i!=5) goto L3; // line 5
i=i+3; // line 6
goto L4; // line 7
L3: // line 8
++i; // line 9
L4: // line 10
goto L1; // line 11
L2: // line 12
; // (Why? read OP...)
}

int pc=1;
int i;

void line1(void) {i=0; ++pc;}
void line2(void) {++pc;}
void line3(void) {printf("%d ",i); ++pc;}
void line4(void) {pc=(i==10)*12+(i!=10)*(pc+1);}
void line5(void) {pc=(i!=5)*8+(i==5)*(pc+1);}
void line6(void) {i=i+3; ++pc;}
void line7(void) {pc=10;}
void line8(void) {++pc;}
void line9(void) {++i; ++pc;}
void line10(void) {++pc;}
void line11(void) {pc=2;}
void line12(void) {exit(0);}

void* program[] = {NULL, line1, line2, line3, line4, line5, line6,
line7, line8, line9, line10, line11, line12};

void execute(void) {
typedef void (*(fnptr))(void);
(*(fnptr)(program[pc]))();
execute();
}

int main (void) {
execute();
}
 
B

Ben Bacarisse

Malcolm McLean said:
Lisp-heads have worked out a whole philosophy of what is needed for
minimal programming. With just the symbols nil and T (lisp for NULL
and 1), and what's called the lambda calculus,

You are not up to scratch with what is minimal in the lambda calculus
(or, indeed, in Lisp). The mathematics (I wouldn't call it "a
philosophy") of what can be done with lambda calculus (and it's close
cousin, combinator calculus) was worked out by mathematicians (not
"lisp-heads") like Church, Rosser, and Curry. It does not need either
truth values or any literal numerals. What's more, it pre-dates both
Lisp and computers (as we know them).
basically the ability to call a function with an arbitrary number of
arguments, and a terminating condition for tail recursion, you can
calculate anything. But only if you're a lisp-head. The rest of us
think in terms of jumps.

I can only assume that the slightly snide term "lisp-head" is intended
to refer to Lisp programmers. If so, then you are not up scratch with
Lisp either. Lisp has gotos, and has had since before C was a twinkle
in a PDP-11's console. "Lisp-heads" have the advantage of thinking in a
wider range of computing paradigms than most other "heads".
 
B

BartC

I can only assume that the slightly snide term "lisp-head" is intended
to refer to Lisp programmers. If so, then you are not up scratch with
Lisp either. Lisp has gotos, and has had since before C was a twinkle
in a PDP-11's console.

(Lisp has go-to?! Thanks! I've been generating Lisp code (part of the same
project that generates C), and that would have been a stumbling block with
certain inputs. I've been having a hard time finding even basic information
about Lisp, as those guys seem to like to keep things complicated.

Now I can translate bad source code to bad Lisp as well as bad C!)
 
B

Ben Bacarisse

BartC said:
(Lisp has go-to?! Thanks!

You're welcome. For reference, it's called "go".
I've been generating Lisp code (part of the
same project that generates C), and that would have been a stumbling
block with certain inputs. I've been having a hard time finding even
basic information about Lisp, as those guys seem to like to keep
things complicated.

Lisp is very well documented. The entire text of "Common Lisp, the
Language" is available online[1], and all the versions of Common Lisp
that I've seen come with either good or reasonable documentation. Like
all very old languages, there are thousands of variations, but the "Lisp
guys" (some of whom might even be gals) took to the ISO standard early
on, so Common Lisp is really the only game in town for anything but
specialist needs.

[1] For example at:
http://www.lispworks.com/documentation/HyperSpec/Front/index.htm
http://www.cs.cmu.edu/Groups/AI/html/cltl/cltl2.html
etc.
 
M

Malcolm McLean

You are not up to scratch with what is minimal in the lambda calculus
(or, indeed, in Lisp). The mathematics (I wouldn't call it "a
philosophy") of what can be done with lambda calculus (and it's close
cousin, combinator calculus) was worked out by mathematicians (not
"lisp-heads") like Church, Rosser, and Curry. It does not need either
truth values or any literal numerals. What's more, it pre-dates both
Lisp and computers (as we know them).
I'm not a mathematician, but my understanding of it is that you clock up
T's on what C programmers would call the call stack, and then you can use
the count of T's to represent an integer. So you need T, nil, and the ability
to execute a call with an arbitrary number of arguments, plus an ability to
terminate the recursion at some point, and you can write any function on
top of that.
I can only assume that the slightly snide term "lisp-head" is intended
to refer to Lisp programmers.
Lispers have a superiority complex, but they are genuinely superior people,
Lisp is taught at Harvard for anyone's sake. The snag is that if you're not
clever enough to get into Harvard you're not clever enough for Lisp, so the
language is to all intents and purposes useless. (it is easy to write an
interpreter for, like everyone else I've had a bash).
If Lispers tease other people, then I'll call them lisp-heads.
 
E

Edward A. Falk

Lisp-heads have worked out a whole philosophy of what is needed for minimal
programming.

Once, as an exercise, I tried implementing Lisp for the 8080. After a
certain point, I was deleting more code than I was writing because I
realized that what I was doing could be implemented using the primitives
I'd already written. I think if I'd kept going, I'd have had the whole
language written with just cons, car, and cdr.
 
B

Ben Bacarisse

Malcolm McLean said:
I'm not a mathematician, but my understanding of it is that you clock up
T's on what C programmers would call the call stack, and then you can use
the count of T's to represent an integer. So you need T, nil, and the ability
to execute a call with an arbitrary number of arguments, plus an ability to
terminate the recursion at some point, and you can write any function on
top of that.

Any good text on the subject will explain how to represent and calculate
with numbers and truth-values in the pure lambda calculus. I'd write it
out here (it's only a few lines) but my off-topic meter has already hit
the red line.
Lispers have a superiority complex, but they are genuinely superior people,
Lisp is taught at Harvard for anyone's sake. The snag is that if you're not
clever enough to get into Harvard you're not clever enough for Lisp, so the
language is to all intents and purposes useless. (it is easy to write an
interpreter for, like everyone else I've had a bash).

Where to start. I knew lots of places that taught Lisp to perfectly
ordinary classes of undergraduates. It was common when "The Structure
and Interpretation of Computer Programs" was all the rage as an
introductory text. There's nothing hard, or elite, about Lisp at all.
You are just perpetuating, or trying to create, some myth about it --
one I've never encountered before. And Harvard appears to favour Java,
at least now.
If Lispers tease other people, then I'll call them lisp-heads.

Good grief, are we six? People who tease others are being jerks. You
should call the out on it, not name-call back!
 
M

Malcolm McLean

Once, as an exercise, I tried implementing Lisp for the 8080. After a
certain point, I was deleting more code than I was writing because I
realized that what I was doing could be implemented using the primitives
I'd already written. I think if I'd kept going, I'd have had the whole
language written with just cons, car, and cdr.
The interpreter is quite easy to write. Basically you have a cons cell
with a "next" and a "child" pointer, which lisp calls car and cdr for
historical reasons. Then you define the nil object. That allows you to
build and walk lists. Then the quote ("don't evaluate this list, it's data")
is needed, together with set which ties symbols to a global namespace,
and lambda.
I checked and lisp lambda works at a slightly higher level of abstraction
than the theoretical lambda calculus. Lisp goes lambda (x, y, z) (+ (* x y) z),
which in C means
int foo(int x, int y, int z) { return x * y + z; }
so it takes an arbitrary number of arguments (but x doesn't have to be
a scalar, it can itself be an expression). The theoretical lambda calculus
makes the specification that the function can take only one argument, which
has to itself be a lambda expression. So where does it end? The answer is you
have a special form, known as the variable, which evaluates to itself. But
the variable doesn't actually have to mean anything, because we can arbitrarily
say that lambda(x)(x) means zero, (call it f) and lambda(x)(f x) means add
one to it. So then we go lamba(x) (lambda(x)(f x) x) means two, and so on on.
So effectively you're piling up Ts on the call stack, in C terms, but they
don't have to be T's. x is a sort of dummy variable.

Whilst the interpreter is basically easy to write, it's harder than it seems
at first sight. The reason is that the expressions aren't very intuitive.
I think I've got the lambdas in my example right, but I wouldn't like to
count on it.

But then you work out how to do absolutely everything you need for programming
with this system. I find this sort of thing very difficult to understand, but
I appreciate its theoretical importance. The comments on Lisp were facetious,
and we need mathematicians to underpin our subject. However its very far
removed from what most people understand by programming.
 
B

Ben Bacarisse

Malcolm McLean said:
The interpreter is quite easy to write. Basically you have a cons cell
with a "next" and a "child" pointer, which lisp calls car and cdr for
historical reasons. Then you define the nil object. That allows you to
build and walk lists. Then the quote ("don't evaluate this list, it's data")
is needed, together with set which ties symbols to a global namespace,
and lambda.
I checked and lisp lambda works at a slightly higher level of abstraction
than the theoretical lambda calculus. Lisp goes lambda (x, y, z) (+ (* x y) z),
which in C means
int foo(int x, int y, int z) { return x * y + z; }
so it takes an arbitrary number of arguments (but x doesn't have to be
a scalar, it can itself be an expression). The theoretical lambda calculus
makes the specification that the function can take only one argument, which
has to itself be a lambda expression. So where does it end? The answer is you
have a special form, known as the variable, which evaluates to itself. But
the variable doesn't actually have to mean anything, because we can arbitrarily
say that lambda(x)(x) means zero, (call it f) and lambda(x)(f x) means add
one to it. So then we go lamba(x) (lambda(x)(f x) x) means two, and so on on.
So effectively you're piling up Ts on the call stack, in C terms, but they
don't have to be T's. x is a sort of dummy variable.

Can I suggest that you don't write about Lisp or the lambda calculus
without a bit more reading first? Almost everything above is wrong.
The Lisp syntax is wrong, the C equivalence is wrong, the encoding of
the numerals is wrong... But since it's off topic, the likelihood is
that it won't be corrected by anyone knowledgeable.

<snip>
 
M

Malcolm McLean

Can I suggest that you don't write about Lisp or the lambda calculus
without a bit more reading first? Almost everything above is wrong.
The Lisp syntax is wrong, the C equivalence is wrong, the encoding of
the numerals is wrong... But since it's off topic, the likelihood is
that it won't be corrected by anyone knowledgeable.
This is why the whole thing has got such an awful reputation. You
write functions to return one and zero, and get them wrong. I don't think
there's any other language where that is possible.
 
B

Ben Bacarisse

Malcolm McLean said:
This is why the whole thing has got such an awful reputation. You
write functions to return one and zero, and get them wrong. I don't think
there's any other language where that is possible.

More of the same confusion. Lisp has numbers just like any other
programming language (well, better than some in many respects, since it
has unbounded integers and rational numbers). You are confusing an
abstract model of computation (the pure lambda calculus) with a
sophisticated and well-developed programming language.

Lisp is easy to program in and is very expressive. A function that
returns zero is, of course, trivial to write. Please stop confusing
people about a language you appear not to know.
 
M

Malcolm McLean

Lisp is easy to program in and is very expressive. A function that
returns zero is, of course, trivial to write. Please stop confusing
people about a language you appear not to know.
The irony is that I've written a Lisp interpreter, that is reasonably
functional. I haven't released it, partly because the garbage collection
isn't in yet, but also because I found it almost impossible to write little
test programs for it and work out what the interpreter was supposed
t actually do with them. I think there's no other language where
the interpreter is easier to write than "hello world".

If lisp was easy to program, the world would be using Lisp, because the
lambda keyword does have a layer of abstraction that C lacks.
 
T

Tim Rentsch

BartC said:
ais523 said:
BartC wrote:

The only way I can think of offhand involves using indexes into
arrays of function pointers; you do arithmetic to choose a "next
function" to call, then index the array to call one of multiple
functions without any conditionals inovlved. You could create
loops using recursion.

You're right of course. I've just had a go at something along
those lines (see code below).

The function testfn() is what I'm trying to execute without using
any control flow statements or conditional (?:) expressions.
It's a silly loop that outputs the sequence 0 1 2 3 4 5 8 9 10.

The first step was to convert into the set of 'flat' statements
shown in testfnflat(), although at this point it still uses the if
and goto statements (now also paired as if-goto) that I prefer.

Then, each line is put into its own function, an array of those
functions is created (program[]), and a 'program counter' is set
up in pc (an index into the function array). If-goto pairs have
to be transformed.

'Control flow' inside this 'function' (since this is now
disseminated throughout the file, and any local variables have
become globals), is done by manipulating the value of pc through
arithmetic.

The function dispatch loop was turned into recursive calls,
relying on an exit(0) in the code to execution (I suppose a stop
flag is simple enough too).

It worked. Although it is more now of a little virtual machine
to execute the code, rather than executing it directly. [snip
program]

Good example! Using a variable to hold a simulated PC shows how
general control flow can be simulated. This approach takes an
imperative programming style.

Another approach is to adopt a functional programming style.
Here is a functional version of the 0 to 10 program. The key
control point is the last line in body(), either calling body()
again or done(), depending on the condition 'i == 10' :

#include <stdio.h>

static int body( int ), done( int );

int
main(){
return body( 0 );
}

int
body( int i ){
printf( " i = %d\n", i );
return (int (*[])(int)){ body, done }[ i == 10 ]( i+1 );
}

int
done( int i ){
return 0;
}

The use of tail recursion allows a compiler to optimize these
calls into jumps. Compiling this program with gcc -O2 produces
object code with only one call instruction, to do the printf.
All other cases were turned into unconditional branches, of
course with the one for the call in body() being an indexed
indirect target (but still done unconditionally). The variable
'i' is effectively a local variable - technically of course it's
a parameter but the compiled code treats it more like a local.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,231
Members
46,820
Latest member
GilbertoA5

Latest Threads

Top