From: | ericmuttta@email.com (Eric) |
Newsgroups: | comp.compilers |
Date: | 20 Jun 2003 00:04:51 -0400 |
Organization: | http://groups.google.com/ |
References: | 03-06-039 03-06-064 |
Keywords: | design |
Posted-Date: | 20 Jun 2003 00:04:51 EDT |
vbdis@aol.com (VBDis) wrote
> ericmuttta@email.com (Eric) schreibt:
>
> >Hmm. To me, "designing" a language, is thinking about problems that
> >need to be solved and then crafting a tool (ie the language) that is
> >suited to solving those problems.
>
> If I understand you correctly, then you care more about features than
> about grammars. In this case you'd have to concentrate on the
> algorithms which are useful in the area of interest, what data
> structures are used by these algorithms, and how and which algorithms
> can work together. These considerations almost go into the design of
> objects (data types and structures) and a library of procedures and a
> runtime system, not into the design of an compiler.
You could say am more feature-oriented than grammar-oriented, but its
hard to separate the two. For every problem the language has to solve,
there is a feature designed specifically to address that problem. And
for every feature, there must be a grammar to define what is a valid
way to express/use that feature in code. I have tried to keep
implementation issues in mind and when choosing/designing features, I
only accept those for which I can come up with a brief and verbal
description of an implementation algorithm.
Its also interesting that you mention designing libraries of objects
and procedures. I have been exploring the issues behind designing
standard libraries and consider the design of these libraries to be as
important as the design of the language itself. Its unfortunate that a
recent trend seems to assume that "big library" is a synonym for
"good/useful library". It sure does make for good advertising when you
can say "the xyz framework consists of over 200 classes...". In fact I
have a theory. We generally (all?) know the statement that our
programs spend 90% of their time executing only 10% of the code. From
this I have derived the fact that only about 25% of any interface is
absolutely crucial and will be used on a regular basis. Try this. Pick
any module/class in a library of your choice. Look through your code
that uses that library, and count the number of procedures and or
fields that you use. Now compare that to the total number of elements
in the entire interface. Try it with your cellular phone feature set,
your car dashboard items, the peripheral ports of your computer, your
favourite word processor etc...only about 25% of the interfaces for
those things are useful. (For a good programming example, if you have
Microsoft's .NET, look at the "System.Windows.Forms.Control" class.
How many of the 150+ elements of that class' interface will you use?)
> As to the runtime system, this can be more or less efficient. But IMO
> this is a more a matter of optimization than of design. I'm not a
> compiler writer myself, so I can give only some obvious hints, as to
> consider the order (complexity) of the required algorithms. Some
> keywords just come into mind, like OO, polymorphism, indirection,
> globals, aliasing, and memory management. I'll not dig deeper into
> these topics now, as long as I'm not sure whether they are of concern
> to you. Only some general statements:
Interesting. I have always thought that the best optimisation is
having a good solid design. Well, am a speed junkie - I love a program
that works and responds without undue delay. IMO, its especially
important to design the run-time system to be as efficient as
possible, simply because EVERY program written using it will suffer if
it isn't efficient - whether that's important for the program or not.
I find it a bit self-defeating to design things poorly, then work so
hard to create optimising compilers. Out of the things you've
mentioned above, I have noticed that the way a language is designed to
handle memory usage and management contributes greatly to its
performance levels.
> Object Orientation IMO is good, even if invisible to the user of a
> language. Every data type with multiple members should become an
> object, with related methods, so that it's clear to the user,
> compiler, and to all procedures, how the information in such an object
> can be accessed, interpreted and modified. Here I consider OO as a
> design technique, with no implications on the concrete implementation.
There are some places where OO shines and other places where it should
be avoided like the plague. For instance, I know of several data types
that have multiple members, but should not be implemented as
objects....This is a sensitive topic however and is usually the cause
of large debates, so suffice it to say that I consider OO a useful
enough paradigm to include in a language, and at the same time, a weak
enough paradigm that needs to be supplemented with other paradigms eg
procedural.
> Polymorphism and indirection is okay, as long as the objects
> themselves are unambigous, so that it's possible to invoke their
> methods without much guessing about the exact type of a given
> object. Calling method X of interface Y of an object is not a very
> complex operation, as long as the type of the interface(s) and the
> pertaining methods are obvious from any given object.
Polymorphism, be it via simple function pointers or the higher level
mechanism provided by OOP, is a powerful and useful concept to have.
However this is also another feature where great care is needed and
there are some rather interesting pitfalls that can occur (below is my
favourite one in VB7):
Module MyModule
Class Parent
Public Overridable Sub p1()
Console.Write("Parent.p1")
End Sub
Public Sub p2()
'blah blah
p1()
'blah blah
End Sub
End Class
Class Child
Inherits Parent
Public Overrides Sub p1()
Console.Write("Child.p1")
MyBase.p2()
End Sub
End Class
Sub Main()
Dim p As Parent
Dim c As Child
p = New Parent()
p.p1() 'OK
p.p2() 'OK
p = New Child()
p.p1() 'stack overflow
p.p2() 'stack overflow
c = New Child()
c.p1() 'stack overflow
c.p2() 'stack overflow
End Sub
End Module
The culprit is in the procedure p1() defined in the Child class. The
statement "MyBase.p2()" calls Parent.p2(). In the Parent class, p2()
calls Parent.p1(). However Parent.p1() is polymorphic so if we have an
instance of Child, the version called is the p1() defined in the Child
class. So Child.p1() calls Parent.p2() which indirectly calls
Child.p1() which calls Parent.p2() which calls.....yep, on and on
until there's no more stack space. Whats "lovely" about this pitfall,
is that the code is perfectly innocent and it fails randomly depending
on whether you have an instance of the Parent class or the Child
class. Its an excercise in masochism to try and find the problem if
the two classes were written separately and the code for the Parent
class isn't available.
> IMO it's a good idea to force the user to explicitly define all
> required interfaces of his objects, not only for the sake of a good
> runtime behaviour, but also for a good application design. Allow for
> compile time checks wherever possible, every check at compile time
> prevents checks and errors at runtime.
I am not sure if I understand you correctly there and could do with a
clarification. Is there any language that allows one to implicitly
define the interfaces to objects?
> Memory management can be implemented with or without garbage
> collection, and with implicit or explicit initialization and
> finalization of local objects. I'll leave it to more experienced
> contributors to discuss these points, as well as the problems with
> global variables and aliasing.
I am not sure how global variables affect implementation (although I
do know their effect on a program's design...you know, the usual
argument against tight coupling) but I know aliasing to be a trouble
maker for optimisers. I think (corrections are welcome) it's to do
with being unable to safely place aliased variables into registers.
> DoDi
Thanks for your comments DoDi.
Cheers, E.
Return to the
comp.compilers page.
Search the
comp.compilers archives again.