Discussion:
FOR loops in Oberon
(too old to reply)
j***@usm.edu
2018-05-22 22:18:02 UTC
Permalink
Hello!

I'm playing with Oberon-07 using the obnc and oberonc compilers (which are great BTW). Today I hit on a surprise: I was sure that Oberon’s FOR loop behaved like that of Pascal, Modula-2, and Ada. That is, I thought the following should print the numbers from 1 to 20:

i := 20;
FOR j := 1 TO i DO
Out.Int(j, 0); Out.Ln;
i := i - 1;
END;

This is admittedly dumb code, but never mind that. I just checked this with freepascal, Gnu Modula-2, and gnat, and all three produce code that prints all the numbers from 1 to 20. I guess this is because the value of i is evaluated at the beginning of the loop, then stored for future tests. This can be more efficient in general, especially if the test involves a complicated function.

In C, on the other hand,it prints the numbers from 1 to 10, because C would evaluate the value of i on each pass through the loop.

It turns out that in code produced by obnc and oberonc, the FOR loop behaves like C, and NOT like the other Pascal-derived languages I mentioned.

I was sure this was a bug: why would Wirth change the semantics of a FOR loop? I went to look in “Programming in Oberon,” and the semantics are explained on p. 21:

It is recommended that the for statement be used in simple cases only;
in particular, no components of the expressions determining the range
must be affected by the repeated statements, and, above all, the
control variable itself must not be changed by the repeated statements.
The value of the control variable must be considered as undefined after
the for statement is terminated.

Again, I concede that the loop above is bad code. Still, I’m curious:

1) Is it an error, because I modify i, violating “no components of the expressions determining the range must be affected”?

2) If so, should the compiler report an error?

3) If not, should the compiler produce code similar to Pascal et al., or identical to C?

I realize there's a workaround. I just want to know what the language lawyers think of it.

sincerely
john perry
Diego Sardina
2018-05-23 04:47:19 UTC
Permalink
In Oberon-07 the FOR statement is just a syntactic sugar of the WHILE statement in the case all elements are to be processed.

It's currently defined as this (assuming inc > 0):

FOR v := beg TO end BY inc DO S END
->
v := beg;
WHILE v <= end DO S; v := v + inc END

As you see, the "end" expression is not copied and it's evaluated in every repetition.

However, in Oberon-07 reports released in 2007, 2011 and 2013 the definition was taken from Oberon-2 as is (that was slightly different from the actual).

v := beg; lim := end;
WHILE v <= lim DO S; v := v + inc END

The end condition is copied to a new variable and the WHILE is tested against that variable. That means the condition after TO is not evaluated in every repetition and is considered constant.

In my opinion, this one is the correct behaviour.

I don't know why in the latest report Prof. Wirth changed the FOR definition. This happened during 2016, IIRC.


--
Diego Sardina
j***@usm.edu
2018-05-23 13:11:11 UTC
Permalink
Post by Diego Sardina
However, in Oberon-07 reports released in 2007, 2011 and 2013 the definition was taken from Oberon-2 as is (that was slightly different from the actual).
v := beg; lim := end;
WHILE v <= lim DO S; v := v + inc END
The end condition is copied to a new variable and the WHILE is tested against that variable. That means the condition after TO is not evaluated in every repetition and is considered constant.
In my opinion, this one is the correct behaviour.
I don't know why in the latest report Prof. Wirth changed the FOR definition. This happened during 2016, IIRC.
That's very interesting. Thank you for pointing that out!
August Karlstrom
2018-05-23 15:25:16 UTC
Permalink
Post by Diego Sardina
I don't know why in the latest report Prof. Wirth changed the FOR definition. This happened during 2016, IIRC.
One way to interpret

v := beg;
WHILE v <= end DO S; v := v + inc END

is that it's left unspecified whether the expression `end' is evaluated
once or each time the guard is evaluated. In that case the compiler
implementor can choose one of the two evaluation strategies and the the
programmer should not rely on any of them.

-- August
j***@usm.edu
2018-05-23 16:06:55 UTC
Permalink
Post by August Karlstrom
Post by Diego Sardina
I don't know why in the latest report Prof. Wirth changed the FOR definition. This happened during 2016, IIRC.
One way to interpret
v := beg;
WHILE v <= end DO S; v := v + inc END
is that it's left unspecified whether the expression `end' is evaluated
once or each time the guard is evaluated. In that case the compiler
implementor can choose one of the two evaluation strategies and the the
programmer should not rely on any of them.
In 9.6, Wirth writes, "The expression evaluation and the statement execution are repeated until none of the Boolean expressions yields TRUE."

The phrase, "the expression evaluation ...[is] repeated" would suggest to me that "end" also has to be evaluated.
j***@usm.edu
2018-05-23 16:07:28 UTC
Permalink
Post by j***@usm.edu
Post by August Karlstrom
Post by Diego Sardina
I don't know why in the latest report Prof. Wirth changed the FOR definition. This happened during 2016, IIRC.
One way to interpret
v := beg;
WHILE v <= end DO S; v := v + inc END
is that it's left unspecified whether the expression `end' is evaluated
once or each time the guard is evaluated. In that case the compiler
implementor can choose one of the two evaluation strategies and the the
programmer should not rely on any of them.
In 9.6, Wirth writes, "The expression evaluation and the statement execution are repeated until none of the Boolean expressions yields TRUE."
The phrase, "the expression evaluation ...[is] repeated" would suggest to me that "end" also has to be evaluated.
Sorry, I forgot to say something: this is especially true if "end" is a procedure, rather than a variable!
c***@gmail.com
2018-05-23 22:28:08 UTC
Permalink
Post by j***@usm.edu
Post by August Karlstrom
Post by Diego Sardina
I don't know why in the latest report Prof. Wirth changed the FOR definition. This happened during 2016, IIRC.
One way to interpret
v := beg;
WHILE v <= end DO S; v := v + inc END
Sorry, I forgot to say something: this is especially true if "end" is a procedure, rather than a variable!
Incorrect. In this example "end" could not be a procedure. If it was, in Oberon-07 it would have to be written as:

WHILE v <= end() DO S; v := v + inc END

Refer to Section 10.1 Formal Parameters.

Regards,
Chris Burrows
CFB Software
http://www.astrobe.com
j***@usm.edu
2018-05-23 22:37:53 UTC
Permalink
Post by c***@gmail.com
Post by j***@usm.edu
Post by August Karlstrom
Post by Diego Sardina
I don't know why in the latest report Prof. Wirth changed the FOR definition. This happened during 2016, IIRC.
One way to interpret
v := beg;
WHILE v <= end DO S; v := v + inc END
Sorry, I forgot to say something: this is especially true if "end" is a procedure, rather than a variable!
WHILE v <= end() DO S; v := v + inc END
Refer to Section 10.1 Formal Parameters.
Sorry; you're right, I knew that (in principle, anyway). I don't see how that changes my point, though; I can't imagine that one would want to evaluate end() only on the first pass through a while loop, but that's what the proposed alternate reading suggests.
August Karlstrom
2018-05-24 08:38:30 UTC
Permalink
Post by c***@gmail.com
Post by j***@usm.edu
Post by August Karlstrom
Post by Diego Sardina
I don't know why in the latest report Prof. Wirth changed the FOR definition. This happened during 2016, IIRC.
One way to interpret
v := beg;
WHILE v <= end DO S; v := v + inc END
Sorry, I forgot to say something: this is especially true if "end" is a procedure, rather than a variable!
WHILE v <= end() DO S; v := v + inc END
Then I'm probably wrong as I thought of `beg', `end' and `inc' as
meta-variables representing expressions.


-- August
August Karlstrom
2018-05-23 18:29:54 UTC
Permalink
Post by j***@usm.edu
Post by August Karlstrom
One way to interpret
v := beg;
WHILE v <= end DO S; v := v + inc END
is that it's left unspecified whether the expression `end' is evaluated
once or each time the guard is evaluated. In that case the compiler
implementor can choose one of the two evaluation strategies and the the
programmer should not rely on any of them.
In 9.6, Wirth writes, "The expression evaluation and the statement execution are repeated until none of the Boolean expressions yields TRUE."
The phrase, "the expression evaluation ...[is] repeated" would suggest to me that "end" also has to be evaluated.
"The expression" refers to the loop guard as a whole, not the limit
expression.

-- August
j***@usm.edu
2018-05-23 18:45:48 UTC
Permalink
Post by August Karlstrom
"The expression" refers to the loop guard as a whole, not the limit
expression.
Even in light of this example from page 13 of "Programming in Oberon," section 6.1?

A simple rule is to avoid expressions within repetitive statements, in which no variable changes its value. For example, the statement

WHILE i < 3*N DO tab[i] := x + y*z + z*i; i := i+1 END

should be formulated more effectively as

n := 3*N; u := x + y*z;
WHILE i < n DO tab[i] := u + z*i; i := i+1 END

I realize it isn't the language definition, but in cases where that is ambiguous this ought to carry *some* weight.
Luca Boasso
2018-05-24 02:41:18 UTC
Permalink
Post by Diego Sardina
I don't know why in the latest report Prof. Wirth changed the FOR definition. This happened during 2016, IIRC.
--
Diego Sardina
While developing the oberonc compiler, I remember asking Prof. Wirth why his implementation of the FOR loop was re-evaluating the "end" expression at each iteration.
In his reply he mentioned that he prefers to avoid the problem of allocating a temporary variable. Indeed, if you look at his latest compiler's source code, there is no other place where a temporary variable must be reserved in the stack frame (I don't consider the save/restore of registers at function calls as temporary variables).
I think this is another example of "make it as simple as possible, but not simpler", the compiler avoid extra complexity, and the end user can always introduce manually the temporary variable in his source code, making his choice explicit.
c***@gmail.com
2018-05-24 04:00:43 UTC
Permalink
Post by Luca Boasso
Post by Diego Sardina
I don't know why in the latest report Prof. Wirth changed the FOR definition. This happened during 2016, IIRC.
--
Diego Sardina
While developing the oberonc compiler, I remember asking Prof. Wirth why his implementation of the FOR loop was re-evaluating the "end" expression at each iteration.
In his reply he mentioned that he prefers to avoid the problem of allocating a temporary variable. Indeed, if you look at his latest compiler's source code, there is no other place where a temporary variable must be reserved in the stack frame (I don't consider the save/restore of registers at function calls as temporary variables).
I think this is another example of "make it as simple as possible, but not simpler", the compiler avoid extra complexity, and the end user can always introduce manually the temporary variable in his source code, making his choice explicit.
I agree - the simpler the definition the less confusing it is to use. It eliminates the seemingly arbitrary conditions associated with FOR loops in other languages. e.g. the control variable can only be a local, the value of the control variable is undefined after the loop has exited etc.
etc.

It should be noted that Wirth initially removed FOR loops in the transition from Modula-2 to Oberon. They were only reinstated some time later (with Oberon-2?).

I find them useful for the really simple loops e.g. initialising an array:

FOR i := 0 TO LEN(a) - 1 DO a[i] := -1 END;

In other cases a WHILE or REPEAT loop is usually a more appropriate choice.
Luca Boasso
2018-05-24 04:18:36 UTC
Permalink
The only thing that I find less convenient is that the range of the control variable goes all the way to the "end" value included: [begin, end]
For example, I would prefer to write

FOR i := 0 TO LEN(a) DO a[i] := -1 END;

where i is in the interval [0, LEN(a)) instead of

FOR i := 0 TO LEN(a)-1 DO a[i] := -1 END;

where i is in the interval [0, LEN(a)-1].

But this is just a matter of taste.
Diego Sardina
2018-05-24 04:57:46 UTC
Permalink
Post by Luca Boasso
The only thing that I find less convenient is that the range of the control variable goes all the way to the "end" value included: [begin, end]
For example, I would prefer to write
FOR i := 0 TO LEN(a) DO a[i] := -1 END;
where i is in the interval [0, LEN(a)) instead of
FOR i := 0 TO LEN(a)-1 DO a[i] := -1 END;
where i is in the interval [0, LEN(a)-1].
But this is just a matter of taste.
Indeed the FOR-WHILE equivalence was offered to give a precise behavior of the FOR statement, since it's not clear if it may include the right limit or not and what value the control variable assumes at the end of repetitions.

This solved the problem that the FOR statement had different behaviors among Modula-2 implementations.

Anyway I agree with you (in Oberon), in Modula-2 this definition was less unpleasant since you had LOW(a) and HIGH(a). However I think that this definition is what makes more sense since we are dealing with (ranges of) integers.
c***@gmail.com
2018-05-24 11:12:52 UTC
Permalink
Post by c***@gmail.com
It should be noted that Wirth initially removed FOR loops in the transition from Modula-2 to Oberon.
The reason given at the time was:

"The elimination of the for statement constitutes a break with another long standing tradition. The baroque mechanism of Algol 60’s for statement had been trimmed significantly in Pascal (and Modula). Its marginal value in practice has led to its absence from Oberon."

From Modula to Oberon (ModToOberon2.Doc / NW 1.10.90)
c***@gmail.com
2018-05-24 11:16:54 UTC
Permalink
Post by c***@gmail.com
Post by c***@gmail.com
It should be noted that Wirth initially removed FOR loops in the transition from Modula-2 to Oberon.
"The elimination of the for statement constitutes a break with another long standing tradition. The baroque mechanism of Algol 60’s for statement had been trimmed significantly in Pascal (and Modula). Its marginal value in practice has led to its absence from Oberon."
From Modula to Oberon (ModToOberon2.Doc / NW 1.10.90)
And then when Oberon-2 was introduced as an extension of Oberon:

"Although for statements can always be expressed by while statements, they are sometimes more convenient because they are shorter and termination is inherent. This is the case if the number of iterations is fixed like in many
applications dealing with arrays."

Differences between Oberon and Oberon–2. H. Mössenböck, N. Wirth Oberon2.Differences.Text (20 Jul 93)
j***@usm.edu
2018-05-24 11:57:07 UTC
Permalink
Post by c***@gmail.com
"Although for statements can always be expressed by while statements, they are sometimes more convenient because they are shorter and termination is inherent.
This is probably no longer true in Oberon-07:

n := 2;
FOR i := 1 TO n DO n := n + 1; END;
Out.Int(i, 0); Out.Ln;

Output is -2147483648 with both obnc and oberonc -- probably not what was expected! Now imagine that the end condition is not a variable n, but a procedure that returns a boolean and depends on n.

john perry
j***@usm.edu
2018-05-24 14:27:10 UTC
Permalink
Now imagine that the end condition is not a variable n, but a procedure that returns a boolean...
*sigh* "boolean" -> "integer"

john perry
j***@usm.edu
2018-05-24 14:28:40 UTC
Permalink
Post by j***@usm.edu
Now imagine that the end condition is not a variable n, but a procedure that returns a boolean...
*sigh* "boolean" -> "integer"
...and even then my suggestion is probably wrong, because eventually i becomes the largest possible integer on the system. So the FOR loop really terminates.

Sorry for the noise.

john perry
Richard
2018-05-25 20:07:15 UTC
Permalink
Post by Luca Boasso
I think this is another example of "make it as simple as possible, but not simpler", the compiler avoid extra complexity, and the end user can always introduce manually the temporary variable in his source code, making his choice explicit.
Also, the generated code can actually be more efficient, because if the
end expression is a constant or a variable in a register, no assignment
to a a temporary variable is necessary.

Richard
Pascal J. Bourguignon
2018-05-26 00:47:55 UTC
Permalink
Post by Richard
Post by Luca Boasso
I think this is another example of "make it as simple as possible,
but not simpler", the compiler avoid extra complexity, and the end
user can always introduce manually the temporary variable in his
source code, making his choice explicit.
Also, the generated code can actually be more efficient, because if the
end expression is a constant or a variable in a register, no assignment
to a a temporary variable is necessary.
Indeed, optimizing compilers can really seem magical. There are some
interesting examples in the case of C compilers, where writing the most
plain and direct source code lead to actually more efficient native
code, than when you try to "optimize" at the source level.

This is why using such compilers as backend for most programming
languages is a reasonable option.

If you want to make a valid comparison, you should compare the
optimization algorithms and trick implemented in the different compiler
backends, and not compare them on the source language basis.
--
__Pascal J. Bourguignon
http://www.informatimago.com
August Karlstrom
2018-05-23 04:59:11 UTC
Permalink
Post by j***@usm.edu
Hello!
i := 20;
FOR j := 1 TO i DO
Out.Int(j, 0); Out.Ln;
i := i - 1;
END;
If you read section 9.8. in the language report you will find that the
above statement sequence is equivalent to

i := 20;
j := 1;
WHILE j <= i DO
Out.Int(j, 0);
Out.Ln;
i := i - 1;
j := j + 1
END

http://miasap.se/obnc/oberon-report.html#sec9.8


Regards,

August
j***@usm.edu
2018-05-23 13:10:15 UTC
Permalink
Post by August Karlstrom
If you read section 9.8. in the language report you will find that the
above statement sequence is equivalent to...
i := 20;
j := 1;
WHILE j <= i DO
Out.Int(j, 0);
Out.Ln;
i := i - 1;
j := j + 1
END
http://miasap.se/obnc/oberon-report.html#sec9.8
I see; I was looking at PiO when perhaps I should have been looking at that document, instead. Are you saying that the reason it is not a syntax error is that "Programming in Oberon" is not as official as the language report?
August Karlstrom
2018-05-23 15:01:08 UTC
Permalink
Post by j***@usm.edu
Post by August Karlstrom
If you read section 9.8. in the language report you will find that the
above statement sequence is equivalent to...
i := 20;
j := 1;
WHILE j <= i DO
Out.Int(j, 0);
Out.Ln;
i := i - 1;
j := j + 1
END
http://miasap.se/obnc/oberon-report.html#sec9.8
I see; I was looking at PiO when perhaps I should have been looking at that document, instead. Are you saying that the reason it is not a syntax error is that "Programming in Oberon" is not as official as the language report?
The language report is of course the reference. There may be a few
places where Wirth has forgot to update PiO in accordance with the
latest revision of Oberon.

-- August
Pascal J. Bourguignon
2018-05-24 18:43:42 UTC
Permalink
Post by j***@usm.edu
Hello!
I'm playing with Oberon-07 using the obnc and oberonc compilers (which
are great BTW). Today I hit on a surprise: I was sure that Oberon’s
FOR loop behaved like that of Pascal, Modula-2, and Ada. That is, I
i := 20;
FOR j := 1 TO i DO
Out.Int(j, 0); Out.Ln;
i := i - 1;
END;
This is admittedly dumb code, but never mind that. I just checked this
with freepascal, Gnu Modula-2, and gnat, and all three produce code
that prints all the numbers from 1 to 20. I guess this is because the
value of i is evaluated at the beginning of the loop, then stored for
future tests. This can be more efficient in general, especially if the
test involves a complicated function.
In C, on the other hand,it prints the numbers from 1 to 10, because C
would evaluate the value of i on each pass through the loop.
It turns out that in code produced by obnc and oberonc, the FOR loop
behaves like C, and NOT like the other Pascal-derived languages I
mentioned.
I was sure this was a bug: why would Wirth change the semantics of a
FOR loop? I went to look in “Programming in Oberon,” and the semantics
It is recommended that the for statement be used in simple cases only;
in particular, no components of the expressions determining the range
must be affected by the repeated statements, and, above all, the
control variable itself must not be changed by the repeated statements.
The value of the control variable must be considered as undefined after
the for statement is terminated.
1) Is it an error, because I modify i, violating “no components of the
expressions determining the range must be affected”?
2) If so, should the compiler report an error?
3) If not, should the compiler produce code similar to Pascal et al., or identical to C?
I realize there's a workaround. I just want to know what the language lawyers think of it.
I would have an orthogonal point of view.

1- both options are equally useful (evaluating the limit once, or
evaluating it on each iteration). The proof is that you have
languages with either semantics that are surviving equally well
during decades.

2- Even if Wirth was God on Earth, his opinion and his specification of
the language may be inconvenient for your own programs. It is
definitely very inconvenient to have to way years on him (or other
benevolent dictators, or on commitees for commitee-defined languages)
to decide for a change of semantics, or a new language feature.

3- even if there are "work arounds", they usually involve boiler
plate. This is never addressed satisfactorily or at all by those
languages.

This is quite surprising, given that since 1964, we know how to design
programming languages so that instead of bearing all those problems, you
can decide for yourself the language features and semantics you want,
and you can implement them yourself, trivially.


(defmacro for/re-evaluate-the-limit ((var start limit) &body body)
(let ((vvar (gensym "VAR-")))
`(prog ((,vvar ,start))
loop
(if (> ,vvar ,limit)
(go end))
(let ((,var ,vvar))
,@body)
(incf ,vvar)
(go loop)
end)))


(defmacro for/evaluate-the-limit-once ((var start limit) &body body)
(let ((vvar (gensym "VAR-"))
(vlimit (gensym "LIMIT-")))
`(prog ((,vvar ,start)
(,vlimit ,limit))
loop
(if (> ,vvar ,vlimit)
(go end))
(let ((,var ,vvar))
,@body)
(incf ,vvar)
(go loop)
end)))


(let ((i 20))
(for/re-evaluate-the-limit (j 0 i)
(format t "~A " j)
(decf i)
(setf j 42))
(format t "~%"))
;; prints: 0 1 2 3 4 5 6 7 8 9 10


(let ((i 20))
(for/evaluate-the-limit-once (j 0 i)
(format t "~A " j)
(decf i)
(setf j 42))
(format t "~%"))
;; prints: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20


I spent a long time in my youth with Pascal, Modual-2, hoping from one
language to the other in search of the perfect programming language.
There's no such thing.
This is why you need a meta-programming language, such as Common Lisp,
where you can shape the language to your own needs and will, right on
the spot, when you have a problem to solve that calls for it.
--
__Pascal J. Bourguignon
http://www.informatimago.com
j***@usm.edu
2018-05-25 01:41:05 UTC
Permalink
Post by Pascal J. Bourguignon
I spent a long time in my youth with Pascal, Modual-2, hoping from one
language to the other in search of the perfect programming language.
There's no such thing.
No disagreement there.
Post by Pascal J. Bourguignon
This is why you need a meta-programming language, such as Common Lisp,
where you can shape the language to your own needs and will, right on
the spot, when you have a problem to solve that calls for it.
I didn't know you could do this in LISP, but I was under the impression that you could do something like this with the most recent Perl (not sure; may have misread something). I immediately wondered, doesn't that run the risk of making code harder to read, if everyone can write his own "dialect"? Perhaps I just don't know enough about this sort of thing.

john perry
Pascal J. Bourguignon
2018-05-25 17:47:01 UTC
Permalink
Post by j***@usm.edu
Post by Pascal J. Bourguignon
I spent a long time in my youth with Pascal, Modual-2, hoping from one
language to the other in search of the perfect programming language.
There's no such thing.
No disagreement there.
Post by Pascal J. Bourguignon
This is why you need a meta-programming language, such as Common Lisp,
where you can shape the language to your own needs and will, right on
the spot, when you have a problem to solve that calls for it.
I didn't know you could do this in LISP, but I was under the
impression that you could do something like this with the most recent
Perl (not sure; may have misread something).
Yes, it's also possible to do something like that in perl.
Also, to some extend, using the C pre-processor, or any other
pre-processor, such as m4.

The main difference between lisp and the other meta-programming systems,
is that lisp source is not the text stored in the source files, but the
data structure that is "deserialised" from this text, ie. the symbolic
expressions (or S-exp). Theres no parenthesis in the S-exps, only
atoms and lists of atoms or S-exps. Processing this tree-like data
structure is easy and much less error-prone than processing text-level
sources. The risk of evil code-injection is basically excluded with you
are processing S-exps to generate code.
Post by j***@usm.edu
I immediately wondered,
doesn't that run the risk of making code harder to read, if everyone
can write his own "dialect"? Perhaps I just don't know enough about
this sort of thing.
Not really.

First, notice that you can write your own "dialect" by using the
standard features of programming language, such as the mere definition
of functions (and you can have even more "fun" with methods and OO).

So theorically, you could indeed write your own dialect or several
dialects inside the same software. In a way, lisp (or any
meta-programming programming language) would be the ultimate tool in
obfuscated code contests. This is the reason why there is no obfuscated
lisp code contest! Way to easy. (perl is obfuscated by default, you
have to work hard to write non-obfuscated perl programs; it's the
epitome of line-noise code). Therefore what happens, is that lisp
programmers are very measured in their use of macros and the definition
of dialects. The first thing we teach to newbies about macros, is not
to write one! (if it can be written as a function).

So you have to be careful not to abuse the power of lisp and its macros
(just like you would avoid abusing the power of C and the tricks you can
do with it). Knowing the eco-system and the culture helps, since over
the time (60 years), some conventions and good practices have emerged
that helps writing code that doesn't break expectations, and that helps
reading the code, even with a lot of "unknown" operators, while still
understanding what it does without having to read the source of the
macros of functions used.

In this respect, the naming of the operators (and other objects of the
language), and the corresponding naming conventions, help a lot, when
respected, and may obfuscate or render the code quite unmaintainable
when broken. This being independent of the programming language. I'm
currently working on some C code, where some functions are named in
quite misleading ways, leading you believe they do something, when in
fact, they do something else barely related to their name!

However, when the problem you have to solve requires a different
paradigm than the usual procedural programming paradigm, you can easily
shape lisp into the paradigm that is best suited to your application.
And you can do it while still integrating nicely with lisp, and
therefore with all the lisp libraries that can themselves be written
using different paradigms. Lisp already includes features supporting
the functional paradigm (it's not a purely functional programming
language like Haskell however). Common Lisp already includes one of the
nicest (and the first having been standardized) Object Oriented system
(CLOS), including a Meta Object Protocol (MOP), to let you define your
own Object System if the one provided by CL wasn't good enough. (Which
happens some times. For example, if you wanted to interface with C++,
which has a very different "Object System", you could define C++-like
classes and metaclasses with the MOP. Other extensions include for
example OODBMS (Object Oriented Database Base Management System).

And finally, YOLO and life is too short to write gratuituous "dialects":
if you have an operator already doing what you want, there's very little
reason to spend time writing your own variant. So this is rarely done.
For example, in the case of our two loop variants, this is already
covered by the LOOP macros provided by CL (which is a whole programming
language in itself, its own iterative paradigms!), so you would never
actually define the macros I defined previously, but in newsgroups or as
teaching examples. ;-)

In normal programs, you'd write:

(loop :with i := 20
:for j :from 0
:while (< j i)
:do (format t "~A " j)
(decf i)
:finally (format t "~%"))

;; prints: 0 1 2 3 4 5 6 7 8 9
;; returns: nil

(loop :with i := 20
:for j :below i
:do (format t "~A " j)
(decf i)
:finally (format t "~%"))

;; prints: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
;; returns: nil


Some argue that loop is not very lispy, since it introduces a syntax
very different from the usual S-exps, so they developped an iterative
package that allows you to express iterations as sophisticated as LOOP or
even more, but using S-exps to define the internal clauses; this is a
good example of users of the programming language disagreeing with the
commitee who defined it, where the users could easily define their own
language constructs.

But again, in a sense, any library is kind of defining your own "dialect".
--
__Pascal J. Bourguignon
http://www.informatimago.com
August Karlstrom
2018-05-25 08:35:00 UTC
Permalink
Post by Pascal J. Bourguignon
This is why you need a meta-programming language, such as Common Lisp,
where you can shape the language to your own needs and will, right on
the spot, when you have a problem to solve that calls for it.
I guess the price you pay for that is an added effort required to
understand a program written in such an ad hoc language. Also, if I'm
not mistaken, Common Lisp is not a system programming language. Nor is
it as efficient as compiled Oberon code.

-- August
Pascal J. Bourguignon
2018-05-25 17:53:46 UTC
Permalink
Post by August Karlstrom
Post by Pascal J. Bourguignon
This is why you need a meta-programming language, such as Common Lisp,
where you can shape the language to your own needs and will, right on
the spot, when you have a problem to solve that calls for it.
I guess the price you pay for that is an added effort required to
understand a program written in such an ad hoc language.
I guess this is why there are actually several operating systems written
in lisp and in Common Lisp :-)

https://common-lisp.net/project/movitz/movitz.html

A more recent example, is this OS that includes even a GUI:
https://github.com/froggey/Mezzano

And of course, there were the Lisp Machines entirely written in lisp
(but pre- Common Lisp).

Oberon seems to be very much inspired from them ;-)


You should probably forget this argument, nowadays people write
Operating Systems in Java Script running in Web browsers!
Post by August Karlstrom
Also, if I'm not mistaken, Common Lisp is not a system programming
language. Nor is it as efficient as compiled Oberon code.
Some CL compilers can generate code even more efficient than some gcc
compilers. I don't know if Oberon compilers have enough resources
invested in them to generate that efficient code: the speed of the code
generated by a compiler doesn't depend on the programming language, but
on the money invested in developping the compiler.

https://www.cliki.net/performance
--
__Pascal J. Bourguignon
http://www.informatimago.com
Pascal J. Bourguignon
2018-05-25 18:03:08 UTC
Permalink
Post by August Karlstrom
Post by Pascal J. Bourguignon
This is why you need a meta-programming language, such as Common Lisp,
where you can shape the language to your own needs and will, right on
the spot, when you have a problem to solve that calls for it.
I guess the price you pay for that is an added effort required to
understand a program written in such an ad hoc language. Also, if I'm
not mistaken, Common Lisp is not a system programming language. Nor is
it as efficient as compiled Oberon code.
I should add, that while I totally agree with Wirth's idea of defining
a programming language so that you can easily write a simple compiler
that is efficient (or efficient enough) to do serious work with it,
writing such a compiler, or even modifying it to add your own feature,
is in general beyond the average programmer or beyond the time allocated
to any programmer (ten-x or normal) by the managers.

So I want to stress that when you extend lisp by writing macros (and in
general meta-programming functions), you do it relying on the existing
lisp compiler: instead of genering binary code, you generate lisp code,
and let the existing compiler compile and optimize your generated lisp
code.

Doing the equivalent with other languages, such as Oberon or C, implies
writing a pre-processor (which requires implementing at least a lexer
and scanner, which again, is not within the reach of most programmers
(unfortunately), or worse, involves hacks (perhaps using regexps!)).
And we know where this path leads: to Objective-C, or worse, to C++!
In any case, this is way more complex than writing a couple of lisp
macros.

The reason why lisp is very sparse in means, is of course because it was
developped on hardware 60 years ago: they didn't have the CPU cycles and
the memory to waste, so a lisp system is fast, by default, on current
hardware. (C, Pascal, Modula-2, Oberon benefit of this too of course,
but they don't have meta-programming features included).


The secret is that macros are normal lisp functions, but hooked into the
compiler. They are compiler plug-ins, in a well structured way,
integrating perfectly in the syntax of the language (the S-exps) and its
semantics.
--
__Pascal J. Bourguignon
http://www.informatimago.com
Continue reading on narkive:
Loading...