Duke Normandin <dukeofperl@[EMAIL PROTECTED]
> wrote:
> So if I wanted to play with all of this as a learning exercise, how
> would I define LINE?
For a learning exercise, you might try it various ways and see how you
adapt.
In practice, an operating system will probably provide a routine to do
it. That routine might require a pair of x,y coordinates. Or it might
require one x,y coordinate and a pair of lengths. It will presumably use
something like a bresenham algorithm which might be built into the
graphics chip. It might possibly have a current point that you can reset
with a separate routine, and it draws from that point to a point you
specify or else draw from that point as many pixels as you specify with
a signed i,j pair.
Whatever it requires, your Forth system might then do a conversion, it
takes the values that the Forth implementor thought you would prefer to
provide, and converts them to whatever the OS wants. They usually assume
you'd rather give endpoints than a starting point and a length.
So whatever you choose for the LINE primitive, then you'll want to
convert (x1, y1, x2, y2) into that so you can call it 4 times to draw
your box.
And the reason this is interesting is that when LINE consumes 4 stack
items, and you have to save 4 items for the next line, the most obvious
way to do it will sometimes have 8 items on the stack which is unwieldy.
Also you'll have to manipulate the top 4 items which is also unwieldy.
There are lots of ways to get around this but all of them take more
thought than using locals.
I don't think anyone has mentioned this one:
: BOX4 ( x1 y1 x2 y2 -- )
2>R
R'@[EMAIL PROTECTED]
OVER 2R@[EMAIL PROTECTED]
LINE
OVER R@[EMAIL PROTECTED]
2R@[EMAIL PROTECTED]
LINE
2DUP OVER R> LINE
R> OVER LINE ;
The return stack gives you easy access to 2 more items. With R'@[EMAIL PROTECTED]
you get
better access to the second return stack item than you do to the third
item on the data stack. But it interferes with DO loops and it
interferes with factoring, and locals will give the same result easier
plus they don't interfere with DO loops.
: BOX4' ( x1 y1 x2 y2 -- )
{ x2 y2 }
2DUP x2 OVER LINE
x2 OVER x2 y2 LINE
OVER y2 x2 y2 LINE
x2 OVER LINE ;
More readable. Probably easier to write. Not as readable and not as easy
as the all-locals approach.
Beating the horse into glue, here's why this is a perennial issue.
People who don't like Forth say, why bother with a data stack at all?
Declare variables, declare locals, let the compiler do the work of
figuring out where everything is. Don't bother your little brain about
it. And Forthers say that with a data stack we get a simpler compiler
and we get simpler subroutine calling. We're encouraged to factor
deeper, and that helps us write simpler code. And they say it isn't
simpler, programming in Forth is like solving a Rubik's cube, you're
handling details the compiler could do better and it costs you in
efficiency.
Superficially it looks like they're right. Here's pseudocode:
proc Dosomething ( integer a, integer b, integer c, integer d ) {
local integer e, integer f, integer g
e := routine1(a, b, c, d)
f := routine2(b, c, d, e)
g := routine3(c, d, e, f)
return e+g
}
Translated directly to Forth we get
: Dosomething ( a b c d -- x )
2OVER 2OVER routine1 \ a b c d e
2OVER 2OVER routine2 \ a b c d e f
2OVER 2OVER routine3 \ a b c d e f g
NIP + NIP NIP NIP NIP ; \ e+g
By not declaring types we get all the advantages and disadvantages of
not checking datatypes. Instead of explicitly passing parameters when we
do calls we put the parameters into comments to keep track. At first
glance the advantages are not obvious. You have to actually do it to see
how it allows and encourages simplicity. So if you look at routine1 2 &
3 and you happen to find that they have redundant code, each of them
calls routine4 ( c d -- h) then you can factor that out.
routine1' ( a b h -- e )
routine2' ( e b h -- f )
routine3' ( f e h -- g )
routine4 ( c d -- h )
: Dosomething ( a b c d -- x )
routine4 \ a b h
2DUP >R >R routine1' \ e R: h b
DUP R> R@[EMAIL PROTECTED]
routine2' \ e e f R: h
OVER R> routine3' \ e g
+ ; \ e+g
We call routines with fewer parameters, they do less work, and when we
change the order of the parameters it's likely the new order will be
better other times we call them. It tends to work out that way. There's
usually a natural order to do things, and it's very often the same
order. When it's easy to improve the routines you call, they tend to
improve.
On the other hand, if you change around a library because the new
routines work better for you, your library is now incompatible with the
standard one. All the vast body of code that uses the old library
doesn't go away, so now you need two libraries. And you've spent time
modifying a library that already worked. You spent time improving a
routine that already worked. You have to do all your tests over again to
make sure it still works. You're spending time thinking when you could
have just cranked something out and gone on to the next problem.
The Forth promise is that the extra thinking will pay off. That the
normal exponential crufty buildup will be slowed. That the simplicities
will work together so that you'll wind up with less total work. Fewer
places for bugs to hide means faster debugging and the possibility for
actual correct code. The less that extra complexity leaves you making
work for yourself, the more time you have available to think and
simplify and the more actual results you can produce.
It's true for some Forth programmers. When people try Forth and decide
it wouldn't work for them, they may be right. The Forth Promise can't
pay off except for programmers who think of ways to keep it simple.
And if you're an employer, do you want to pay people to think or do you
want to pay them to crank out solutions? One way you get lots of
solutions. Bang. Bang. Bang. Bang. Bang. Bang. Six problems solved
quickly. If your employee spends more time to solve 4 problems and he
changed the problems around to suit himself, how do you decide whether
the time is worth it? It's obviously stupid to measure productivity by
lines of code, but what do you use instead? There are people who try to
measure functionality with function points etc, but I predict that
automated methods to do that will fail, and that to do it correctly
takes competent programmers and requires more time than it does to
produce the code in the first place. For employers it's a leap of faith
to believe the Forth promise. They might believe it from their own
previous experience or from anecdotal evidence. They tend not to believe
it, and Forth has a tiny market share -- mostly limited to customers who
desperately need it to be true.


|