Re: shitty programming [WAS: Chucks address]
- To: Jaap van Ganswijk <ganswijk@xxxxxxxxx>, MISC
- Subject: Re: shitty programming [WAS: Chucks address]
- From: Greg Alexander <galexand@xxxxxxx>
- Date: Fri, 16 Jun 2000 10:05:45 -0500
- In-Reply-To: Your message of "Fri, 16 Jun 2000 03:58:37 +0200." <3.0.6.32.20000616035837.01eff100@pop.xs4all.nl>
- Sender: galexand@xxxxxxxxxxxxxxxxxxxxxxxx
>>FORTH.
>
>Forth is not the world's programming language, but C and it's
>derivatives are. C is a language that's much more adapted to
>humans than to the underlying hardware and is therefore much
>more writable and readable and rereadable and checkable
>than a reversed polish notation one-type-only based language.
Not true. When I first started reading C it had these asterisks and
ampersands everywhere. It took me a good year and a half of idly
tinkering around before I was confident that I *knew* pointers. The type
system still throws me for a loop every now and then with SIMPLE OPERATOR
PRECEDENCE, and I've used the language quite a bit for the last 6 years.
After investing about 3 [on and off, it was a hobby in my highschool
years] years in C, I was confident that I could read any code presented to
me and have good odds of figuring out what it did, but maybe not what it
was meant to do (the code was perfectly readable, I could tell that a=b
assigns a value, etc., but the bad factoring nearly forced by C could make
the idea the code represents much more cloudy). You can't make + its own
function in C because if you did things would be 50x slower. Making + its
own function in FORTH results in maybe a 3x slowdown from traditional code
and there are lots of ways to get around that. Maybe nobody wants to
factor out +, but nobody in C would factor out a function that they knew
would only contain one other function call (in Pygmy FORTH, though, COUNT
is defined as C@+ without hesitation or stack gymnastics -- in C you may
not be doing the stack gymnastics but you know that factoring at that
level would result in stack gymnastics somewhere).
The essence is that all languages are hard to read before you know
them, except maybe BASIC, and that's because nobody can write anything in
BASIC. You can point to languages you picked up in a couple hours and
assume that they must be easy to read...like Perl...but Perl is not easy
to read...it is easy for me to read because I knew it long before I ever
saw it. You need to remember that infix is not easy to read, it's merely
a language you already know. Once you learn FORTH, you can read and write
it just fine. I'm very new to FORTH and even already I can read it much
better than I could before.
>But even if you should be one the sekte that disagrees, any
>generally employable processor should be able to handle
>compiled C programs quite well also.
Depends on what you mean by 'generally employable processor.' MISC is not
meant to be on the desktop of every technoweenie. MISC makes a perfect
embedded chip, a perfect chip for portable devices that don't need to be
reprogrammed by the unwashed masses, but the C world simply isn't suited
to MISC. I'm not even talking about the fact that it would be hard
(though not as hard as you may think) to compile C for a MISC chip, but
because a C head doesn't want what a FORTH head wants. A C head thinks
caching is a good idea. A C head thinks that a 10x performance loss here
and there adds up to an n*10 times performance loss, not a 10^n times
performance loss. A C head thinks that bending over backwards to make
the code look like something the compiler can optimize is a day well
spent. A C head
thinks that $100 is a reasonable amount to spend for a mass-produced
processor. A C head doesn't seem to care that his machine has fans in it.
A C head couldn't live without a harddrive. A C head expects to have the
language, and not understand how the compiler works, and then to also
write a shell for the computer, so that he has that further level between
him and his chosen language. A C head would rather buy more RAM so he can
use the bloated OSs of the day rather than writing his own.
>>Preferably running on a MISC chip. But the MISC chip
>>is not an absolute requirement.
>
>Yes, Chuck is God, Jess is Jezus and Dr. Ting is the holy
>spirit!
I think there's something wrong with me. I can't find many people I can
work with. Most people, working with them is tutoring them. I don't
think I'm very smart, but every time I'm asked to work on programming or
anything with someone else they turn out to be much less interested, much
less knowledge, and much less able than I. There are exceptions, but they
haven't been in my life yet.
On the other side I see a lot of programs produced in situations where
people do work side by side with idiots. Linus is a definite genius, a
great coder, reading the linux-0.01 source was the best operating systems
introduction I could ever imagine. But absolute idiots write kernel
drivers. As a result I see that one of my CD-ROMs barely works and
another doesn't work at all. I can't very well replace these drivers
because another set of idiots has decided that CD-ROMs are proprietary top
secret stuff and nobody documents these things.
So I've seen from a lot of directions the message that teamwork is not the
road to perfection. Its got its uses if the ends are more important than
the means, but if you're programming for the joy of it...if your code is
going to be beautiful when you're done...it's best to do it right, and
that almost never means teamwork. There are situations where a team of
GREAT people can be formed, but it just doesn't happen often enough to be
worth depending on. If I find myself in such a team, a lot will change,
but for right now I know that finding such a team is outside of my reach.
So what does this mean? It means that simplicity is necessity. If I'm to
implement my own processor I know I won't be able to design the processing
parts and pass it on to someone else to lay out the cache, and someone
else to verify the out of order execution buffer, and someone else to hack
on an FPU, etc. So I would design, like Chuck did, a very small
processor.
If I were to build my whole computing system, I know I would not be able
to build a very good C compiler. Those things are very complicated. I
know I couldn't build much of a Unix OS -- Linus built quite a bit but
even he had a lot of contributions. I wouldn't want to build both a
language and a shell...that's two languages, why bother? So I know any
system I would be able to call my own would not be very similar to the
system that I use today.
In the end, it turns out, if I were to build my own system I would do it
all the way that Chuck, Jeff, Ting, etc. have. I'd design a simple
processor. I'd use a simple language. I'd eliminate things that I know I
won't need. So while I have a certain awe for Linus because of both
his technical genius and his incredible human abilities that have allowed
the Linux kernel to not turn into a BSD factionary mess of politics, if I
were to worship anyone it would be much more likely for me to worship
Chuck, Jeff, Ting, etc. Not only are they as interested, knowledge, and
talented as I (or much more so!), but they think like I do. They make
the same type of systems I would, and for a lot of the same reasons. This
isn't to say I actually worship them -- Chuck and Ting are fairly distant
and Jeff is merely human. None of them have quite the same humility and
understanding of people that's allowed Linus to lead a project that's
changing the world. But at least they're doing things that I'm interested
in, in ways and for reasons that I can relate to.
So, yes, it is with the same type of religion that I chose vi over
emacs that I chose FORTH and computer cowboys (lowercase) over the Unix
world.
If you want to see what happens when a single person who thinks
more or less like me decides to build his own system, look at Sphinx C--.
It's just a compiler, but it's clear that the goals were centered around
simplicity. It is a LOT more simple tahn your average C compiler and even
still it's huge and very complicated, compared to FORTH. Why simplify
something complex when you could start with something already fairly
simple?
>>>Procedures/functions will need local
>>>memory so they will need some sort of stack mechanisme.
>>>It's also very inexpensive, just decrement the stack pointer
>>>and push the instruction pointer.
>>
>>Sucks for passing data on the stack.
>
>Everything is cachable in this world. I'm just saying that a
>stack is needed. See it in a more abstract way.
A stack is needed, but stack FRAMES are not. This seems like the sort of
thing that a C head would discover on an acid trip but probably dismiss
because of ignorance of FORTH. (the night I discovered forth.org's
webpage was more mindbending and enlightening than any acid I've ever met,
though). Tail recursion, which I think is now universally respected, for
example, involves a big mindbend: the idea that you can REUSE a stack
frame. It's a small step from there to realize that really, you want to
detach the return addresses from the data -- then you can dispose of the
whole idea all together and gain incredible flexibility.
>>And of course the programmer is no longer in charge. The compiler writer is.
>
>Yes and the programmer lives in an easier more abstract world.
>Only bit-fuckers don't like this. Believe me, I probably know
>more assembly languages than you, have used them and have
>written more disassemblers, assemblers and compilers for them,
>but it has always been my conviction that programmers should
>waste their time as less as possible writing in assembler.
That's the fundamental flaw of C. They have the assembler, then they have
C, then they have the shell on top of that. If they just want to add two
numbers, there's no good reason not to just use the assembler. If they
just want to call a system call there's not any good reason to just use
the C stuff. In FORTH we integrate these levels. There's still an
assembler, but it's more integrated and is almost part of the compiler on
a MISC chip. If you want to add two numbers you use + -- it doesn't
matter if you're at the FORTH prompt or if you're deep in the middle of
some to-be-compiled code. The interpretter and compiler are nearly
identical. In a C world if you want to change the volume of your
soundcard you run a program that calls an ioctl that tells the OS to tell
the soundcard to lower the volume. Really there's no good reason not to
have direct keyboard access to the driver call that lowers the volume.
The driver call that lowers the volume would take maybe two items on the
stack: the device identifier (a number in my way of thinking), and the
volume we want. You could define constants for the device identifiers.
So you type CD_VOL 0 MIXER at the FORTH prompt and the CD becomes muted.
If you don't like typing so much then at the FORTH prompt itself, without
bothering to specially make a file then invoke a compiler on it, you can
just type : CD_VOL CD_VOL SWAP MIXER ; then you can do 0 CD_VOL to mute
the CD. This is every bit as useful and abstract as using a unix mixer
and typing 'mix --cd 0' at the Unix prompt. We've got all of the
simplicity and ease of use, but we don't have any of the useless layering.
We've still got the driver between us and the sound card, so we don't need
to remember any port addresses or IO protocols. Eventually we'll find a
very easy to use sound card where MIXER would be defined like this:
: MIXER SWAP VOL_PORT + ! ; ( VOL_PORT is a constant)
there we have extreme simplicity, and we still have THE INTERFACE WE
DESIRE!! (as a side note, I'm beginning to think that it would've been a
better idea, both for programming and for thinking, to have it be '0 CD_VOL
MIXER' rather than 'CD_VOL 0 MIXER' -- I won't bother to change it now
though, perhaps as an example of why it's important to think about what
you really want and how much you can simplify these things if you make
small changes in your input).
Suppose you make a solitaire program. You may have a word called MOVE
(which takes a source deck number, a destination deck number, and the
number of cards to try to move), and a word called PRINT to print the
desk to the screen. A C head in this case would then implement words on
top of this to form a loop that calls PRINT, then asks for your move in
text input form parses it, then pushes it on the stack, then calls print.
Me? I just rename MOVE to M, and PRINT to P, and I have a perfectly
workable user interface RIGHT THERE, with no extra effort. It turns out
the level of abstraction I wanted was ALREADY THERE, I didn't need to add
anything. That is the trick to abstraction: try to make it so that you
get it for free. I added one extra word, TRY (which I renamed to T),
which takes a source deck number and tries to move all of the movable
cards from that deck onto each of the other decks until it finds one where
it goes. In this way the vast majority of the time I type merely 'x T'
where x is a number from 0 to 11 (0-6 are the ones you start with that are
mostly face down, 7-10 is the destination where you want to put your aces
and so on, 11 is the main deck you get your new cards from). I
implemented 'NEXT_CARD' that takes a new card from the invisible deck and
puts it onto deck 11. Now I defined the following:
: NC 3 FOR NEXT_CARD NEXT P ;
: NC1 NEXT_CARD P ;
So with very very little effort, I have a very usable user interface. I
don't have to do much typing. If I put it on a computer with a mouse, I
could easily use Richard Fergus's point and do interface [Forth Dimension,
XX.3) or similar to add a layer to convert mouse drags into calls to M.
By renaming all of my useful words to be single letters I've reduced the
amount of typing during gameplay to be comparable to any other solitair
implementation and I find this to be actually MORE natural than any other
solitaire interface that I can imagine running on my palmtop. Using the
arrow keys would introduce an unnatural preference for using decks that
are close together. Any other interface would turn out essentially the
same in terms of how much work I do as typist/user. I keep one thumb over
the numeric keypad and one over the alphabetic keypad. If I bought a palm
pilot that uses a pen, I would add the necessary word to handle the pen
drags to make this use that interface.
I hope that these two lengthy examples have been sufficient to demonstrate
to you that you don't need a lot of levels in order to get a level of
abstraction over the machinery that a human being finds QUITE comfortable.
The only disadvantage is safety. That is a serious disadvantage, but
cleverness can introduce words here and there to check the input. For
example, my M word starts off with a call to a word called 'CHECK' that
makes sure it's a valid move. Underflowing the stack is a big problem,
but it's trivial to make even a simple traditional forth have a couple
cells under the stack that contain a magic number that the interpretter
checks to tell you if you've underflowed. If you use Chuck's circular
stacks then a stack underflow is no problem whatsoever, just a bit of an
inconvenience.
>>I have spent many an hour when designing in C discussing
>>what the compiler might or might not be doing to our code.
>
>You shouldn't have to discuss that. When you're an application
>programmer just trust the compiler and go for portability.
That would be misplaced trust.
>When you really need the most optimum speed out of your
>hardware, look at how the compiler has translated things
>and rewrite those parts in assembler or rewrite the compiler.
>Been there, done that.
Rewriting a FORTH compiler is something you can do in a weekend.
Rewriting a C compiler is something you can do in a month. *shrug*
>>>When you want you can
>>>also use a frame pointer but for C it's not strictly needed.
>>>All displacements on the stack (compared to the stack
>>>pointer) are always known.
>>
>>At the compiler level. God forbid you might have to interface some
>>assy language to this kludge. Can be done of course. Just bring
>>big buckets of $$$$$$.
>
>You're thinking of CISC. In RISC, the SP isn't changed
>within a function. The compiler can provide variable names
>for the variables that are easily usable on the assembler level.
>The compiler/assembler combination that I used (and wrote
>myself) did that. It was for the 6811. But I managed to write
>almost everything efficiently in C. Only taskswitching and
>interrupt handling required some (in C embedded) assembler
>statements.
>
>But the 6811 is a very easy processor to write a good C
>compiler for.
>
>>I LOVE C.
>
>C is the optimum in a certain class of languages (C, Basic,
>Algol, Cobol, Fortran, Pascal, PL/1 etc.).
I also love C. It doesn't mean that FORTH isn't better for most things.