I nominate this post for inclusion in some extended tutorial documentation
somewhere, so it can get wider distribution to people trying to learn
Factor. Very nice!
On Jul 4, 2013 10:16 AM, Alex Vondrak ajvond...@gmail.com wrote:
OK, I've gone about implementing this on my own from the Wikipedia
article, and I have a few salient points about your implementation.
To preface, the general issue I took with doLine was that it was doing too
much work in one word. As you said, the code is rather C-like. But I
think it's a bit of a chicken-or-the-egg problem because of all the
variables you need for Bresenham's algorithm.
One feature of this algorithm is how much state you have to keep around:
dx, dy, sx, sy, err, x0, x1, y0, y1, intermediate variables... You
naturally start using locals, but pretty soon you have to cram too much
into one word since, well, locals are hard to factor out. They are local
after all, so the one big word is where you have access to your variables
for modification (already looking like a C-based program...). It's hard to
justify factoring out a helper word like
```
:: doLine ( ax ay bx by code -- codeout )
bx ax - abs : dx
by ay - abs : dy
dx dy - : err
ax : cx
ay : cy
ax ay bx by cx cy dx dy err code some-helper-word ;
```
because `some-helper-word` still has to take about a million variables in
from the stack, resigning it to a locals-laden implementation. It doesn't
really simplify anything. Plus, the loop has to destructively update these
locals at some point, so even with helper words you'll have to do something
like
```
var1 var2 var3 helper-word var1! var2! var3!
```
It's just not very useful.
But what to do? After implementing it myself, I have a few general ideas:
1. To cut down on all these variables, store your state in its own tuple.
This is a Factor-ism that doesn't crop up often, but is a pretty natural
way to ease up on complex parameter passing and stack shuffling.
2. As I suspected, `with-return` helps with what you're trying to
accomplish, while alleviating the need to worry about the extra `continue`
parameter.
3. This is not really part of your formulation, but it came up in the
version I was trying to write. If you ever find yourself attempting to
build a sequence, but the logic doesn't fit into a ready-made combinator
(like `map` or `follow` or `produce`), the `make` vocabulary is perfect:
http://docs.factorcode.org/content/article-namespaces-make.html As an
added bonus, it works via side-effect, which helps eliminate stack
shuffling.
4. As the stack shuffling gets tighter, start trying to tease out words
from the big definitions. Factoring out what you can, locals ideally
become the artifact of only a couple small words, rather than the crux of
one huge word.
---
Consider this a sort of spoiler alert. :) In case you want to see how I
did it with these points in mind, here's a walk-through of my version. The
full code is available at http://paste.factorcode.org/paste?id=3021 if
you'd prefer to try understanding it on your own (I find that sometimes
reading code is more edifying than having it explained).
To keep the state in its own tuple, I actually defined two classes:
```
TUPLE: point x y ;
C: point point
TUPLE: bresenham-state
{ p point }
{ q point }
{ dx integer }
{ dy integer }
{ sx integer }
{ sy integer }
{ error integer } ;
```
This lets me initialize the state of the algorithm and manage it all in
one instance of an object. To update the state, just update the slots of a
`bresenham-state` instance. (I know `p` and `q` are conventional names for
quotations, but I think they serve well as names of points here.)
I use the following code to initialize the algorithm's state. Notice how
I factor out operations into small words. Mind you, there are about a
million ways to do this, all about equally understandable, so this is
really the least important part of the program:
```
: delta ( p q quot: ( point -- coord ) -- delta )
bi@ - abs ; inline
: initialize-deltas ( state -- state )
[ ] [ p ] [ q ] tri
[ [ x ] delta dx ]
[ [ y ] delta dy ] 2bi ;
: step ( p q quot: ( point -- coord ) -- step )
bi@ 1 -1 ? ; inline
: initialize-steps ( state -- state )
[ ] [ p ] [ q ] tri
[ [ x ] step sx ]
[ [ y ] step sy ] 2bi ;
: initialize-error ( state -- state )
[ ] [ dx ] [ dy ] tri - error ;
: bresenham-state ( p q -- state )
\ bresenham-state new
swap q
swap p
initialize-deltas
initialize-steps
initialize-error ;
```
Words (currently, anyway:
https://github.com/slavapestov/factor/issues/758#issuecomment-16514324)
have to be defined in Factor bottom-up, but for the purposes of this
narrative, I'll explain my implementation of the algorithm in a top-down
fashion.
Starting with the main loop of the algorithm, notice how I use