YAPL - Yet Another Programming Language

J

Juha Nieminen

Type1 foo(Type2 value)
{
for(size_t i = 0; i < data.size(); ++i)
for(size_t j = 0; j < data.size(); ++j)
for(size_t k = 0; k < data[j].size(); ++k)
if(meetsRequirements(data[j][k], value)
return data[j][k];

}

Your function returns garbage when nothing meets the requirements.


Most compilers will issue a warning, though.
A stuctured solution to your example could use a boolean flag which
is changed when the data is found.

Exactly how is that different from 'break'? (Let's assume a 'break'
which can take as parameter how many nested loops it breaks from, like
some languages have. In other words, you could do a "break 3;" in the
example above, assuming it was supported.)

The only difference I can see is that the 'break' would be
compiler-supported (and thus easier for the compiler to optimize), while
the boolean solution is user-written, which is more work and clumsier.
Also, I don't believe the boolean solution would make the code any
easier to understand and follow than the 'break' solution.
// Find the first value in 'data' which meets the requirements
// imposed by the parameter:
Type1 foo (Type2 value)
{
int search = 1;
Type1 result = Type1default;

for (size_t i = 0; i < data.size() & search; ++i) {
for (size_t j = 0; j < data.size() & search; ++j) {
for (size_t k = 0; k < data[j].size() & search; ++k) {
if (meetsRequirements(data[j][k], value) {
result = data[j][k];
search = 0;
}
}
}
}
return result;
}


Another point: Assume that initializing the return value is a very
heavy operation. If a result is found, you are initializing the return
value twice, for no reason. (If the found value was returned from inside
the loop, and if the loops end without result, the default value is
returned, the return value would then always be initialized only once.)
In fact, it would be enough for the default value to be very heavy to
construct for this problem to happen.

Sure, you could add yet another boolean to take care of that... Is
this supposed to make the code easier to write and read, and/or less
error-prone?
As you can see, I prefer to use curly braces even when they are not
necessary.

That's mostly a matter of style and preference. I omitted them mostly
for brevity (ie. it doesn't mean I consistently avoid the braces in real
code whenever I can).

Btw, why do you use the braces in an inconsistent way?
In a language which has no C for loops like Seed7 the function
would look as follows:

const func Type1: foo (in Type2: aValue) is func
result
var Type1: result is Type1.value;
local
var integer: i is 1;
var integer: j is 1;
var integer: k is 1;
var boolean: search is TRUE;
begin
while i <= maxIdx(data) and search do
while j <= maxIdx(data) and search do
while k < maxIdx(data[j]) and search do
if meetsRequirements(data[j][k], aValue) then
result := data[j][k];
search := FALSE;
end if;
incr(k);
end while;
incr(j);
end while;
incr(i);
end while;
end func;


I'm sorry but I have to confess that doesn't look like very attractive
to me. The 9 lines of code in my original function (with the fix of
returning the default value at the end) vs. 23 lines in your example. I
honestly can't say your version is more readable either.

Let's make it clear that I'm all against compact obfuscated code.
However, there's a limit where code becomes *too* verbose to be comfortable.
I know that this solution is not as elegant.
Maybe I should introduce an advanced version of the 'for' statement
like:

for i range minIdx(data) range maxIdx(data) andWhile search do

What do you think?

A 'for' clause is always a handy shortcut for 'while', so why not.

I also think you should really consider 'break' (perhaps even the
version which takes a parameter, as I mentioned above).
 
J

James Kanze

[I've removed comp.lang.c from the cross-postings, since the
syntax of the proposed solutions is purely C++.]

Type1 foo(Type2 value)
{
for(size_t i = 0; i < data.size(); ++i)
for(size_t j = 0; j < data.size(); ++j)
for(size_t k = 0; k < data[j].size(); ++k)
if(meetsRequirements(data[j][k], value)
return data[j][k];
}

Your function returns garbage when nothing meets the requirements.

Most compilers will issue a warning, though.
Exactly how is that different from 'break'?

It gives a name to the condition, and makes it clear to the
reader what is going on. However...

A structured solution would break this down into several
functions. In the next release of C++ (with lambda), you could
easily do the whole thing with a single call to std::find_if:
the predicate for std::find_if on the outer dimension would
invoke std::find_if on the next dimention, and so on.

Alternatively, since you're really doing a linear search over
the flattened structure, you'd use an iterator which flattened
the structure. I'm pretty sure that Boost has something which
would help here.
(Let's assume a 'break' which can take as parameter how many
nested loops it breaks from, like some languages have. In
other words, you could do a "break 3;" in the example above,
assuming it was supported.)

That is, of course, a real trap. Change the structure, and your
code silently changes meaning. Having a one level break is bad
enough.
The only difference I can see is that the 'break' would be
compiler-supported (and thus easier for the compiler to
optimize), while the boolean solution is user-written, which
is more work and clumsier. Also, I don't believe the boolean
solution would make the code any easier to understand and
follow than the 'break' solution.
// Find the first value in 'data' which meets the requirements
// imposed by the parameter:
Type1 foo (Type2 value)
{
int search = 1;
Type1 result = Type1default;
for (size_t i = 0; i < data.size() & search; ++i) {
for (size_t j = 0; j < data.size() & search; ++j) {
for (size_t k = 0; k < data[j].size() & search; ++k) {
if (meetsRequirements(data[j][k], value) {
result = data[j][k];
search = 0;
}
}
}
}
return result;
}

Another point: Assume that initializing the return value is a very
heavy operation.

Why? Since I have to consider the possibility of not finding
the value, I'd use a Fallible:

Fallible< Type1 > result ;
for ( size_t i = 0 ; ! result.isValid() && i != data.size() ; ++
i ) {
for ( size_t j = 0 ; ! result.isValid() && j != data.size() ; +
+ j ) {
for ( size_t k = 0 ; ! result.isValid() && k !=
data.size() ; ++ k ) {
if ( meetsRequirements( data[ i ][ j ][ k ], value ) {
result.validate( data[ i ][ j ][ k ] ) ;
}
}
return result ;

Initializing a Fallible to invalide is a very cheap operation,
involving setting a boolean flag (and nothing else in my current
implementations).
 
J

Juha Nieminen

James said:
Type1 foo(Type2 value)
{
for(size_t i = 0; i < data.size(); ++i)
for(size_t j = 0; j < data.size(); ++j)
for(size_t k = 0; k < data[j].size(); ++k)
if(meetsRequirements(data[j][k], value)
return data[j][k];
}

A structured solution would break this down into several
functions.

How exactly would that help? The calling function would still have to
check if the called function found the value or not, and end the loop if
so. But of course, loops cannot be ended from inside, except with the
boolean variable. So how is this a better solution?
In the next release of C++ (with lambda), you could
easily do the whole thing with a single call to std::find_if:
the predicate for std::find_if on the outer dimension would
invoke std::find_if on the next dimention, and so on.

The outer dimension would still have to stop if the inner dimension
found the value. How does it do that?
That is, of course, a real trap. Change the structure, and your
code silently changes meaning. Having a one level break is bad
enough.

I prefer breaks over having to use extraneous useless booleans which
do nothing but make the code less readable, and possibly even less
efficient.

It's not like breaks would stop you from doing it in a purely
structured manner. They are optional.
 
W

Willem

Juha Nieminen wrote:
) James Kanze wrote:
)> In the next release of C++ (with lambda), you could
)> easily do the whole thing with a single call to std::find_if:
)> the predicate for std::find_if on the outer dimension would
)> invoke std::find_if on the next dimention, and so on.
)
) The outer dimension would still have to stop if the inner dimension
) found the value. How does it do that?

In a hypothetical language, where this is a 'lambda' function with single
parameter, called 'a', returning the value of do_something_with:

{ a | do_something_with(a) }

And 'or' is a shortcut operator so that a or b equals a ? a : b

This finds the first result in an array:

find_it = { arr, func |
if (arr) { func(arr[0]) or find_it(arr[1..], func) }
else { undef }
}
result = find_it(array, { a| meetsReqs(a) ? a : undef } );

And this is a way to find the first result in a 3-D array:

result = find_it(array3, { array2 |
find_it(array2, { array1 |
find_it(array1, { a| meetsReqs(a) ? a : undef })
})
});

But, of course, given an is_array() function, this finds the first result
in an N-dimensional array:

array_find = { func, a |
if (is_array(a)) { find_it(a, array_find(func)) or undef }
else { func(a) or undef }
}
result = find_it(array, array_find(func));

NB: The result of calling a function with less arguments is a function
where those arguments are filled in. For example, if plus() adds
its two arguments then you can do: increase2 = plus(2);

Lots of features, huh ? But languages have existed for a long time that
have all of them. Some of them even resemble C. (Say, LPC)


SaSW, Willem
--
Disclaimer: I am in no way responsible for any of the statements
made in the above text. For all I know I might be
drugged or something..
No I'm not paranoid. You all think I'm paranoid, don't you !
#EOT
 
J

Juha Nieminen

Willem said:
In a hypothetical language, where this is a 'lambda' function with single
parameter, called 'a', returning the value of do_something_with:

{ a | do_something_with(a) }

And 'or' is a shortcut operator so that a or b equals a ? a : b

This finds the first result in an array:

find_it = { arr, func |
if (arr) { func(arr[0]) or find_it(arr[1..], func) }
else { undef }
}
result = find_it(array, { a| meetsReqs(a) ? a : undef } );

And this is a way to find the first result in a 3-D array:

result = find_it(array3, { array2 |
find_it(array2, { array1 |
find_it(array1, { a| meetsReqs(a) ? a : undef })
})
});

But, of course, given an is_array() function, this finds the first result
in an N-dimensional array:

array_find = { func, a |
if (is_array(a)) { find_it(a, array_find(func)) or undef }
else { func(a) or undef }
}
result = find_it(array, array_find(func));

This starts resembling Haskell. Exactly as obfuscated. ;)
 
W

Willem

Juha Nieminen wrote:
) This starts resembling Haskell. Exactly as obfuscated. ;)

Actually, I had Perl in mind when I was writing it, although I was very
liberal in how I wrote stuff, so the resemblance is small.

Talking about Perl, obfuscated, and functions, how about this piece
of code, demonstrating ADT and data hiding:

sub create_stack
{
my @stack; # Only accessible from within this function, right ?
return ( sub { push @stack, @_ }, sub { pop @stack } );
}

my ($pusher, $popper) = create_stack();

$pusher->('x');
$pusher->('y');
print $popper->();
$pusher->('z');
print $popper->();
print $popper->();


Who needs OOP ? :)


SaSW, Willem
--
Disclaimer: I am in no way responsible for any of the statements
made in the above text. For all I know I might be
drugged or something..
No I'm not paranoid. You all think I'm paranoid, don't you !
#EOT
 
J

James Kanze

James said:
Type1 foo(Type2 value)
{
for(size_t i = 0; i < data.size(); ++i)
for(size_t j = 0; j < data.size(); ++j)
for(size_t k = 0; k < data[j].size(); ++k)
if(meetsRequirements(data[j][k], value)
return data[j][k];
}

A structured solution would break this down into several
functions.

How exactly would that help? The calling function would still
have to check if the called function found the value or not,
and end the loop if so. But of course, loops cannot be ended
from inside, except with the boolean variable. So how is this
a better solution?

Well, the inner loop is simple a classical linear search:
std::find_if, in sum, or written out in long:

size_t index = 0 ;
while ( index < size && meetsRequirements( data[ index ] ) ) {
++ index ;
}
return index ;

The next loop working out is also just a linear search, calling
the above function as its "condition".

And so on.
The outer dimension would still have to stop if the inner
dimension found the value. How does it do that?

By using the results of the find_if on the inner loop as its
predicate.
I prefer breaks over having to use extraneous useless booleans
which do nothing but make the code less readable, and possibly
even less efficient.

Except that used correctly, it makes the code more readable.
There's almost nothing that hurts readability more than a break
or a return hidden somewhere in the middle of a nested loop,
where it has no business. A variable has a name, which makes it
clear what is happening.

But in the exact case you presented, you don't really want
either: you've got a classical linear search (std::find_if)
whose condition is the results of another linear search, whose
condition is the results of still another linear search.

This is 2008. One would expect that any competent programmer
would know what a linear search is, and how to write one if
there's not already one available in the library.
It's not like breaks would stop you from doing it in a purely
structured manner. They are optional.

Sure, but if you use them, you're no longer structured, and the
code quickly becomes completely unmaintainable.
 
A

Adem24

Juha Nieminen said:
Adem24 said:
1) There is no goto statement.
Hidden goto's like break- and continue-statements are also omitted.

2) There is no return statement.
Instead a result variable can be declared to which the result of a function can be assigned.

Is the goal of this language to make the life of programmers as hard
as possible? Something like this becomes difficult to implement:

// Find the first value in 'data' which meets the requirements imposed
// by the parameter:
Type1 foo(Type2 value)
{
for(size_t i = 0; i < data.size(); ++i)
for(size_t j = 0; j < data.size(); ++j)
for(size_t k = 0; k < data[j].size(); ++k)
if(meetsRequirements(data[j][k], value)
return data[j][k];
}


I do the same :)

My nested loop techniques:

1) If I need to return a value then I use this method (the same as above):

for (size_t i = 0; i < data.size(); ++i)
for (size_t j = 0; j < data.size(); ++j)
for (size_t k = 0; k < data[j].size(); ++k)
if (meetsRequirements(data[j][k], value)
return data[j][k];


2) If I need to break nested loops without using 'goto' then I use this method:
TCLoopBreaker is a tiny helper class I wrote for this purpose.
It is a "direct access stack".
When I find time to document it then I'll post it here.
It is simply a directly accessible stack implemented using a std::vector

size_t i, j, k; // loop vars

TCLoopBreaker<size_t> LB;
LB.push(i, true);
LB.push(j, true);
LB.push(k, true);

for (i = 0; i < data.size(); ++i)
for (j = 0; j < data.size(); ++j)
for (k = 0; k < data[j].size(); ++k)
if (meetsRequirements(data[j][k], value)
{
//... do whatever you want here

LB.breakall(); // this break's all loops
}


3) Optimization for both cases:

If the size of the data containers don't change in these loops,
then, if possible, one better can/should use a const helper, like here:

const size_t ie = data.size(),
je = data[0].size(),
ke = data[0][0].size();

for (size_t i = 0; i < ie; ++i)
for (size_t j = 0; j < je; ++j)
for (size_t k = 0; k < ke; ++k)
if (meetsRequirements(data[j][k], value)
return data[j][k];
 
T

thomas.mertes

(e-mail address removed) said:
[...] Just because the compilers of most programming languages use
a table of hardcoded reserved words does not imply that this is the
only solution.

True enough. My own preferred solution is to divide the world into two
namespaces. User namespace begins with an alphabetic character, and *no*
language feature may interfere with that - i.e. no keywords or library
routines may begin with an alphabetic letter.
Algol had something like that (although implementation dependend).
Every keyword had to be quoted or should start with a dot.
Alas, some languages don't
even follow /their own/ namespace schemes, let alone mine. (For example, C
introduced the restrict keyword in C99. User space? Ha! Stomp stomp
stomp...)
My solution to this is: Every program must request the language
version it is written in. Currently C would have 3 versions: K&R,
C89 and C99. Compilers should take this into account and old
programs could be used without change. Seed7 is better suited for
this feature than C, since all it's statements are defined in the
include file "seed7_05.s7i". Therefore another include file could
define a different language.
Oh, I know. I just found it quite amusing that, within a hundred or so
minutes of its being requested, I was able not only to design a language
that met the specification, but also implement a (portable!) compiler for
it and provide a sample program written in that language. (Within those
hundred minutes I also devised a *de*compiler, which I didn't bother to
publish.) I'm not sure whether anyone has actually got the whole joke
yet...
Actually I got your joke quickly. I just did not want to talk about
esoteric languages like Ook!
Anyway, FWIW, creativity is what counts. Keep coming up with new ideas, and
eventually some of them *might* get through the thick skulls of the CS
community.
It is not my intention to drill holes in thick skulls.
I see it more as an offer:

Seed7 is open source and it is an
extensible programming language.

Everybody can insert his ideas by using the extensibility
mechanisms or by changing the source. That means that
even the outdated concepts discussed in this thread
could be inserted. It is just not my intention to do so.

Greetings Thomas Mertes

Seed7 Homepage: http://seed7.sourceforge.net
Seed7 - The extensible programming language: User defined statements
and operators, abstract data types, templates without special
syntax, OO with interfaces and multiple dispatch, statically typed,
interpreted or compiled, portable, runs under linux/unix/windows.
 
S

santosh

All those things are exactly what most modern programming languages
try to omit/discourage (except point 4). I'm not surprised what you
say in point 2 about not being there much to say about grammar, since
4 of your 5 points have grammatical errors. :)

Actually, IMHO, all his points (except for 5) have some merit, though he
hasn't worded them well.
 
T

thomas.mertes

Actually, IMHO, all his points (except for 5) have some merit, though he
hasn't worded them well.
Are you speaking about the 10 points of the original poster
or about the 5 points mentioned by "rio"?

If you were speaking about the 10 points of the OP:
How would you write them?

Greetings Thomas Mertes

Seed7 Homepage: http://seed7.sourceforge.net
Seed7 - The extensible programming language: User defined statements
and operators, abstract data types, templates without special
syntax, OO with interfaces and multiple dispatch, statically typed,
interpreted or compiled, portable, runs under linux/unix/windows.
 
T

thomas.mertes

The World Joint Programming Language Standardization Committe (WJPLSC)
hereby proclaims to the people of the world that a new programming language
is needed ...
It is interesting to see that a programming language is attacked
for being "too restrictive" and beeing "too liberal" at the same
time. Reasons why Seed7 can be seen as too liberal:

1) User defined statements and operators.

2) Statements like 'for' or 'while' are declared in the language
itself.

3) Types are first class objects.

4) Templates and generics can be defined easily without special
syntax.

6) Abstract data types like arrays and structs are declared in the
language itself.

7) User definable abstract data types.

8) Object orientation is based on interfaces.

9) Supports multiple dispatch (not to be confused with
multiple inheritance).

10) Allows to connect methods to objects.

11) Overloading of procedures/functions/operators/statements

12) Various predefined types like strings, arrays, hashes,
bitsets, structs, bigInteger, bigRational, time, duration,
color, etc.

13) Resizable arrays where the upper and lower bound can change.

Before answering, how stupid it is to support user defined
statements, I suggest to consult the Seed7 FAQ:
http://seed7.sourceforge.net/faq.htm

Seeing that there are user defined statements and that the whole
thing is open source should wipe away all "too restrictive"
arguments:

1) If you think that a feature is missing you can add it using
the extensibility features of Seed7.

2) In case the extensibility features are not powerful enough
for your super duper statement, you still have the possibility
to change the source of the interpreter and compiler (all code
in the Seed7 package is under the GPL/LGPL).

As always: Any feedback is welcome.

Greetings Thomas Mertes

Seed7 Homepage: http://seed7.sourceforge.net
Seed7 - The extensible programming language: User defined statements
and operators, abstract data types, templates without special
syntax, OO with interfaces and multiple dispatch, statically typed,
interpreted or compiled, portable, runs under linux/unix/windows.
 
R

Richard Bos

Sorry, but Seed7 (the list is an almost exact copy from the Seed7
manual) does not support this. The keyword 'if' is not reserved in
the classic sense, but the syntax defined for 'if' requests that an
expression follows it. Since variables are not written this way, it
is not possible to define a variable named 'if'.

Ok, so you have no reserved keywords, but you have specific, predefined
words which are reserved for certain contexts and have certain meanings.
Right.
I presume that you also have no "goto" statement, but you have this
"jump" syntax definition which just happens to change program flow? And
that you have no distinction between functions and statements, but that
some statements can be called from other statements and some can not?
It can be subsumed

That word does not mean what you think it means.
that Seed7 reaches the goal of avoiding the misuse of keywords in
other ways and not by reserving them altogether.

It can also be assumed that George Bush Junior didn't reach the goal of
being president by getting Jeb to bend the rules in Florida and not by
outright fraudulent behaviour. Nevertheless...

Richard
 
M

Matthias Buelow

Just because the compilers of most programming languages use
a table of hardcoded reserved words does not imply that this is the
only solution.

True, for example, (Common) Lisp doesn't have any reserved words; you
can redefine anything, and still access the original interpretation
through the common-lisp package (namespace). One can even rebind many of
those although in practice, this is of course discouraged (and, by
default prevented, by most Lisp systems).
 
S

santosh

Are you speaking about the 10 points of the original poster
or about the 5 points mentioned by "rio"?

The latter. I didn't give much attention to the starting post because it
was clearly an attempt to troll.
If you were speaking about the 10 points of the OP:
How would you write them?

I think points 3, 4 and 7 are admirable, but the others points seem to
me to be overly restrictive. But then much of my experience with PLs
have been with "traditional" ones like C, C++, assembler, BASIC etc.
Certainly it seems paranoid, to me, to dispense with unconditional
jumps, default parameters, and explicit return statements. To much
striving for abstraction or "elegance" can also, IMO, lead to an overly
complex language. But I have mostly used PLs to construct programs and,
to be honest, haven't given the design of the languages themselves much
thought, so you would want better feedback than mine.
 
T

thomas.mertes

Type1 foo(Type2 value)
{
for(size_t i = 0; i < data.size(); ++i)
for(size_t j = 0; j < data.size(); ++j)
for(size_t k = 0; k < data[j].size(); ++k)
if(meetsRequirements(data[j][k], value)
return data[j][k];
}

Your function returns garbage when nothing meets the requirements.


Most compilers will issue a warning, though.


Of course.
Btw. If you look at the Seed7 homepage at
http://seed7.sourceforge.net
you will see that it supports user defined statements.
That way missing features can be added.

When I thought about adding statements I had 'select' statements
and similar constructs in my mind, but there is generally no
restriction.

So it would be possible to add goto, break, continue and return
statements. But I don't think that this is the way to go.
There is a tendency to replace gotos with structured solutions:
- Standard Pascal has no return, break or continue statement.
- Java has no goto statement

Instead of introducing all features present in any language (Seed7
could do that, at least to some degree) it think that care should be
taken when a new feature like a statement is added.

I would say that in most cases structured programs (where every
construct has one entry and one exit) are easier to read (at least
when you are used to it). But there are exceptions, like the one you
wrote. Such exceptions (where a 'return' makes the code simpler)
are an indication that some structured statements still may be
missing.

My question is: How should a loop statement look like when you
want to avoid a 'return' in your example? At the same time the
code should still be elegant to some degree.

One possibility would be a

for key i range data andWhile search do

loop. With this loop the foo() function would look like.

const func Type1: foo (in Type2: aValue) is func
result
var Type1: result is Type1.value;
local
var integer: i is 1;
var integer: j is 1;
var integer: k is 1;
var boolean: search is TRUE;
begin
for key i range data andWhile search do
for key j range data andWhile search do
for key k range data[j] andWhile search do
if meetsRequirements(data[j][k], aValue) then
result := data[j][k];
search := FALSE;
end if;
end for;
end for;
end for;
end func;

This is just a draft and values which are heavvy to initialize
are a different issue. But maybe you have a better idea.

As you can see: I suggest we use the hardest way when designing
a language (instead of introducing all features that are
theoretically possible) in the hope that the users can benefit
from this efforts.

The benefit of such structured programming is, that you can always
add some logging (or other) statement at the end of a function, and
the execution of that statement is guaranteed.

I have seen programs where maintenance statements (such as free() )
have been ommitted unintentionally at some of the returns.

Greetings Thomas Mertes

Seed7 Homepage: http://seed7.sourceforge.net
Seed7 - The extensible programming language: User defined statements
and operators, abstract data types, templates without special
syntax, OO with interfaces and multiple dispatch, statically typed,
interpreted or compiled, portable, runs under linux/unix/windows.
 
T

thomas.mertes

Ok, so you have no reserved keywords, but you have specific, predefined
words which are reserved for certain contexts and have certain meanings.
Right.
Wrong. The interpreter does not know about any reserved words.
Seed7 uses syntax declarations like:

$ syntax expr: .if.().then.().end.if is -> 25;

They are gathered in the include file "syntax.s7i". The syntax
declaration above specifies what is to be expected after the keyword
'if'. The core of the syntax declaration is interesting:

if () then () end if

It defines the syntax of a simple if statement. The symbols () are
nonterminal symbols while 'if', 'then', 'end' and 'if' are terminal
symbols. Terminal symbols which are used in at least one syntax
declaration are called keyword. The interpreter does
not hardcode the keywords somewhere.

I am preparing a chapter about syntax declarations. It will describe
the details of them. As soon as I have something together I plan to
release a preview chapter in the comp.programming newsgroup.
I presume that you also have no "goto" statement, but you have this
"jump" syntax definition which just happens to change program flow?
Bzzzzt. Wrong. There are primitive actions upon which the statements
are based. There are primitive actions for if, while, repeat, for,
case and other statements. But there is no primitive action for goto,
jump or whatever you are thinking of to change the program flow in an
unstructured way. If there is really a desire to add a goto statement
it would be necessary to add a corresponding primitive action.
And
that you have no distinction between functions and statements, but that
some statements can be called from other statements and some can not?
Totally wrong. There is a type system which takes care to check for
types statically. There are types which are used for closures. Such
a closure type is helpful when defining a statement (with some
statement syntax). But functions (which use the classic function
call syntax) can also use closures.
That word does not mean what you think it means.
Thank you for the hint. Read instead:

It can be summarized
[totally unrelated statement snipped]

Greetings Thomas Mertes

Seed7 Homepage: http://seed7.sourceforge.net
Seed7 - The extensible programming language: User defined statements
and operators, abstract data types, templates without special
syntax, OO with interfaces and multiple dispatch, statically typed,
interpreted or compiled, portable, runs under linux/unix/windows.
 
A

Adem24

Type1 foo(Type2 value)
{
for(size_t i = 0; i < data.size(); ++i)
for(size_t j = 0; j < data.size(); ++j)
for(size_t k = 0; k < data[j].size(); ++k)
if(meetsRequirements(data[j][k], value)
return data[j][k];

}
Your function returns garbage when nothing meets the requirements.

Most compilers will issue a warning, though.


Of course.
Btw. If you look at the Seed7 homepage at
http://seed7.sourceforge.net
you will see that it supports user defined statements.
That way missing features can be added.

When I thought about adding statements I had 'select' statements
and similar constructs in my mind, but there is generally no
restriction.

So it would be possible to add goto, break, continue and return
statements. But I don't think that this is the way to go.
There is a tendency to replace gotos with structured solutions:
- Standard Pascal has no return, break or continue statement.
- Java has no goto statement

Instead of introducing all features present in any language (Seed7
could do that, at least to some degree) it think that care should be
taken when a new feature like a statement is added.

I would say that in most cases structured programs (where every
construct has one entry and one exit) are easier to read (at least
when you are used to it). But there are exceptions, like the one you
wrote. Such exceptions (where a 'return' makes the code simpler)
are an indication that some structured statements still may be
missing.

My question is: How should a loop statement look like when you
want to avoid a 'return' in your example? At the same time the
code should still be elegant to some degree.

One possibility would be a

for key i range data andWhile search do

loop. With this loop the foo() function would look like.

const func Type1: foo (in Type2: aValue) is func
result
var Type1: result is Type1.value;
local
var integer: i is 1;
var integer: j is 1;
var integer: k is 1;
var boolean: search is TRUE;
begin
for key i range data andWhile search do
for key j range data andWhile search do
for key k range data[j] andWhile search do
if meetsRequirements(data[j][k], aValue) then
result := data[j][k];
search := FALSE;
end if;
end for;
end for;
end for;
end func;

This is just a draft and values which are heavvy to initialize
are a different issue. But maybe you have a better idea.

As you can see: I suggest we use the hardest way when designing
a language (instead of introducing all features that are
theoretically possible) in the hope that the users can benefit
from this efforts.

The benefit of such structured programming is, that you can always
add some logging (or other) statement at the end of a function, and
the execution of that statement is guaranteed.

I have seen programs where maintenance statements (such as free() )
have been ommitted unintentionally at some of the returns.


You unfortunately do not adress one important aspect in programming:
portable fast code. Ie. your above technique is IMO not efficient
as it tests the search flag with each increment of the loop var.
See my previous posting where I adressed also the "Optimization" issue
and gave an example on how to break deeply nested loops.
The "breakall" technique for nested loops I presented
can easily be implemented in any language using standard elements
of the language.


BTW, my OP which initiated this thread was just a parody against dictatorship etc... :)
Take it with humor please :)
 
T

thomas.mertes

(e-mail address removed) wrote:
Type1 foo(Type2 value)
{
for(size_t i = 0; i < data.size(); ++i)
for(size_t j = 0; j < data.size(); ++j)
for(size_t k = 0; k < data[j].size(); ++k)
if(meetsRequirements(data[j][k], value)
return data[j][k];
}
Your function returns garbage when nothing meets the requirements.
Most compilers will issue a warning, though.

Of course.
Btw. If you look at the Seed7 homepage at
http://seed7.sourceforge.net
you will see that it supports user defined statements.
That way missing features can be added.
When I thought about adding statements I had 'select' statements
and similar constructs in my mind, but there is generally no
restriction.
So it would be possible to add goto, break, continue and return
statements. But I don't think that this is the way to go.
There is a tendency to replace gotos with structured solutions:
- Standard Pascal has no return, break or continue statement.
- Java has no goto statement
Instead of introducing all features present in any language (Seed7
could do that, at least to some degree) it think that care should be
taken when a new feature like a statement is added.
I would say that in most cases structured programs (where every
construct has one entry and one exit) are easier to read (at least
when you are used to it). But there are exceptions, like the one you
wrote. Such exceptions (where a 'return' makes the code simpler)
are an indication that some structured statements still may be
missing.
My question is: How should a loop statement look like when you
want to avoid a 'return' in your example? At the same time the
code should still be elegant to some degree.
One possibility would be a
for key i range data andWhile search do
loop. With this loop the foo() function would look like.
const func Type1: foo (in Type2: aValue) is func
result
var Type1: result is Type1.value;
local
var integer: i is 1;
var integer: j is 1;
var integer: k is 1;
var boolean: search is TRUE;
begin
for key i range data andWhile search do
for key j range data andWhile search do
for key k range data[j] andWhile search do
if meetsRequirements(data[j][k], aValue) then
result := data[j][k];
search := FALSE;
end if;
end for;
end for;
end for;
end func;

This is just a draft and values which are heavvy to initialize
are a different issue. But maybe you have a better idea.
As you can see: I suggest we use the hardest way when designing
a language (instead of introducing all features that are
theoretically possible) in the hope that the users can benefit
from this efforts.
The benefit of such structured programming is, that you can always
add some logging (or other) statement at the end of a function, and
the execution of that statement is guaranteed.
I have seen programs where maintenance statements (such as free() )
have been ommitted unintentionally at some of the returns.

You unfortunately do not adress one important aspect in programming:
portable fast code. ...

I have some experience in producing portable fast programs. Just
look at the speed of the hi (Seed7) interpreter. 200000 lines per
second are not uncommon. There are also cases where my bigInteger
library (written in portable C) is faster than GMPs (written partly
in assembler). But there are always new things to learn and there is
always room to improve.

The general consensus about optimisations is:

1) Write a program in a way that makes it readable.
2) Make sure that the program works as exprected.
3) If it is too slow, use compiler optimisation flags.
4) If it is still too slow do benchmarks.
5) Think over better algorithms for the slow parts.
6) When this is not enough, use lower optimisation technics.
7) Do benchmarks to verify that a speedup has been reached.

For library optimisation there are additional rules:

- Try to do performance tests for a wide range of testsets.
- Try to do optimisations for this wide range even when
your program seems to be fast enough.

Seed7 uses several technics which help to reach good performace

- It does static type checks
- A static function dispatch is done, when possible.
- Seed7 can be compiled to C.
- There is a possibility to pass-through the -O option to the
C compiler.
Ie. your above technique is IMO not efficient
as it tests the search flag with each increment of the loop var.
This check will be optimized away by the C compiler.
In the innermost loop it is neccesary to check

meetsRequirements(data[j][k], aValue)

as long as the element which meets the requirements is not found or
until the size of the array is reached.

A good optimizing compiler (such as gcc) will recognize that there
is only one place where the boolean flag 'search' is set to TRUE.
Therefore it can remove the check for the 'search' flag in the loop
condition by a jump out of the loop when 'search' is set to TRUE.
In the outer loops similar replacements are done.

In the end both versions (with return and with boolean flag) will
produce very similar machine code. If you don't belive me, I suggest
you do some benchmarks.
See my previous posting where I adressed also the "Optimization" issue
and gave an example on how to break deeply nested loops.
The "breakall" technique for nested loops I presented
can easily be implemented in any language using standard elements
of the language.

An optimizing compiler can produce good code for this "break from a
deep nested loop" without any stack manipulating tricks done by a
smart programmer.

Have you ever looked at the machine code produced by an optimizing
compiler? The code is so heavily changed to a clever network of
gotos, that it is hard to recognize the original statements. And
at this level I think that goto's are ok.

I think that you underestimate the optimisations done by todays
compilers totally. It has been said that in most cases todays
compilers produce better machine code than hand crafted assembler
written by humans. Probably fans of assembler know 1000 examples
where this is not the case...
BTW, my OP which initiated this thread was just a parody against dictatorship etc... :)
Take it with humor please :)

It's okay.
Generally I do not like to be compared with dictators.

It is NOT my intention to set any form of dictatorship among the
programmers. The Seed7 package contains the work of many years and
I give it away for free under the GPL. Seed7 is an offer and
everybody is invited to join the Seed7 community.

If you think that Seed7 is missing features, you are invited to add
them.

Some programming languages are dominated by big companies. They earn
money by pushing their languages to the developers. Do you think
that this are the good guys?

Btw. Seed7 has also been critisized for beeing too liberal. There
are people who find the idea of user defined statements and
operators frightening.

As always: Any feedback is welcome.

Greetings Thomas Mertes

Seed7 Homepage: http://seed7.sourceforge.net
Seed7 - The extensible programming language: User defined statements
and operators, abstract data types, templates without special
syntax, OO with interfaces and multiple dispatch, statically typed,
interpreted or compiled, portable, runs under linux/unix/windows.
 

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,780
Messages
2,569,611
Members
45,276
Latest member
Sawatmakal

Latest Threads

Top