Pages

20190406

Thinking Forth by Leo Brodie


  • To call forth a concept, a word is needed; to portray a phenomenon, a concept is needed.
  • Programming computers can be crazy making.
  • Programmers design, build, and repair the stuff of imagination, ghostly mechanisms that escape the senses.
  • Our work takes place not in RAM, not in an editor, but within our own minds.
  • Forth is a language, an operating system, a set of tools, and a philosophy.
  • Assembly-language programming is characterized by a one-for-one correspondence between each command that the programmer types and each command that the processor performs.
  • High-level languages are clearly more “powerful” than assembly languages in the sense that each instruction might compile dozens of machine instructions. But more significantly, high-level languages eliminate the linear corresponding between source code and the resulting machines instructions.
  • Decisions about the algorithms and associated data structures should be made in parallel.
  • Simplicity is the primary measurement recommended for evaluating alternative designs relative to reduced debugging and modification time.
  • By dividing a problem into simple modules, programs were expected to be easier to write, easier to change, and easier to understand.
  • The safest kind of coupling is the passing of local variables as parameters from one module to another.
  • The smallest atom of a Forth program is not a module or a subroutine or a procedure, but a “word”.
  • Everything in Forth is a word.
  • Calls are implicit.
  • Data passing is implicit.
  • Because Forth uses a stack for passing data, words can nest within words.
  • Forth eliminates from our programmes the details of how words are invoked and how data are passed.
  • Because Forth is interactive, the programmer can type and test the primitive commands.
  • Forth programming consists of extending the root language toward the application, providing new commands that can be used to describe the problem at hand.
  • Forth is a programming environment for creating application-oriented languages.
  • The purpose of hiding information is simply to minimize the effects of a possible design-change by localizing things that might change within each component.
  • Forth conveniently seperates “things” from “actions” by allowing addresses of data structures to be passed on the stack and providing the “fetch” and “store” commands.
  • Forth pays little attention to whether something is a data structure or an algorithm. This indifference allows us programmers incredible freedom in creating the parts of speech we need to describe our applications.
  • Forth is a design language.
  • Unfortunately, human foresight is limited even under the best conditions. Too much planning becomes counterproductive.
  • Constructing a prototype is a more refined way to plan, just as breadboarding is in electronic design.
  • Experimentation proves more reliable in arriving at the truth than the guesswork of planning.
  • Overall, Forth outdoes all other high-level languages in speed, capability, and compactness.
  • Although Forth is an interpretive language, it executes compiled code.
  • Forth’s use of a data stack greatly reduces the performance cost of passing arguments from word to word.
  • Forth can do anything any other language can do--usually easier.
  • Forth can be written to run on top of any operating system or, for those who prefer it, Forth can be written as a self-sufficient operating system including its own terminal drivers and disk drivers.
  • Start simple. Get it running. Learn what you’re trying to do. Add complexity gradually, as needed to fit the requirements and constraints. Don’t be afraid to restart from scratch.
  • Testing and prototyping are the best ways to discover what is really needed.
  • For newcomers to Forth: Keep the analysis phase to a minimum.
  • For Forth addicts: Hold off on coding as long as you possibly can.
  • Plan for change (by designing components that can be changed).
  • Prototype.
  • The first step is to determine what the application should do.
  • A conceptual model is an imaginary solution to the problem. It is a view of how the system appears to work.
  • A design begins to describe how the system really works.
  • Strive to build a solid conceptual model before beginning the design.
  • First, and most importantly, the conceptual model should describe the system’s interfaces.
  • Decide on error- and exception-handling early as part of defining the interface.
  • Develop the conceptual model by imagining the data traveling through and being acted upon by the parts of the model.
  • Forth encourages you to think in terms of the conceptual model, and Forth’s implicit use of a data stack makes the passing of data among modules so simple it can usually be taken for granted. This is because Forth, used properly, approaches a functional language.
  • Most of your efforts at defining a problem will center on describing the interface.
  • Visualization of ideas helps in understanding problems, particularly those problems that are too complex to perceive in a linear way.
  • You don’t understand a problem until you can simplify it.
  • Keep it simple.
  • Given two solutions to a problem, the correct one is the simpler.
  • Generality usually involves complexity. Don’t generalize your solution any more than will be required; instead, keep it changeable.
  • Go back to what the problem was before the customer tried to solve it. Exploit the “don’t cares”.
  • To simplify, quantize.
  • To simplify, keep the user out of trouble.
  • To simplify, take advantage of what’s available.
  • Careful planning is essential, because things always take longer than you expect.
  • The mean time for making a “two-hour” addition to an application is approximately 12 hours.
  • Conventional wisdom reveres complexity.
  • Always give your client some options. Clients like options.
  • Everything takes longer than you think, including thinking.
  • To see the relationship between two things, put them close together. To remind yourself of the relationship, keep them together.
  • The goal of preliminary design is to determine what components are necessary to accomplish the requirements.
  • Within each component, implement only the commands needed for the current iteration. (But don’t preclude future additions.)
  • Definitions are invoked by being named.
  • The purpose of an interface component is to implement, and hide information about, the data interface between two or more other components.
  • Both data structures and the commands involved in the communication of data between modules should be localized in an interface component.
  • We must distinguish between data structures that are validly used only within a single component and those that may be shared by more than one component.
  • Express in objective units any data to be shared by components.
  • One of Forth’s rules is that a word must already have been defined to be invoked or referred to.
  • Most of us are guilty of over-emphasizing the difference between “high-level” and “low-level”. This notion is an arbitrary one. It limits our ability to think clearly about software problems.
  • An object is a portion of code that can be invoked by a single name, but that can perform more than one function. To select a particular function you have to invoke the object and pass it a parameter or a group of parameters.
  • Don’t bury your tools.
  • A component is simply a set of commands that together transform data structures and algorithms into useful functions. These functions can be used without knowledge of the structures and/or algorithms within.
  • By thinking about the ways in which we solve problems, apart from the problems themselves, we enrich our subconscious storehouse of techniques.
  • Determine your goal.
  • Picture the problem as a whole.
  • Develop a plan.
  • Set a course for action and avoid the trap of fumbling about aimlessly.
  • Think of an analogous problem.
  • Work forward.
  • Work backward.
  • Belief is a necessary ingredient for successfully working backward.
  • Recognize the auxiliary problem.
  • Step back from the problem.
  • It’s easy to get so emotionally attached to one particular solution that we forget to keep an open mind.
  • Use whole-brain thinking.
  • When a problem has you stumped and you seem to be getting nowhere, relax, stop worrying about it, perhaps even forget about it for a while.
  • Creative people have always noted that their best ideas seem to come out of the blue, in bed or in the shower.
  • Evaluate your solutions. Look for other solutions.
  • Don’t invest too much effort in your first solution without asking yourself for a second opinion.
  • The human mind works exceptionally well with analogies.
  • Each definition should perform a simple, well-defined task.
  • In designing a component, the goal is to create a lexicon that will make your later code readable and easy to maintain.
  • Let numbers precede names.
  • Let text follow names.
  • Let definitions consume their arguments.
  • Use zero-relative numbering.
  • Since computers are numeric processors, software becomes easier to write when we use zero-relative numbering.
  • Let addresses precede counts.
  • Let sources precede destinations.
  • Avoid expectations (in the input stream).
  • Let commands perform themselves.
  • Don’t write your own interpreter/compiler when you can use Forth’s.
  • A simple solution is one that does not obscure the problem with irrelevancies.
  • An algorithm is a procedure, described as a finite number of rules, for accomplishing a certain task. The rules must be unambiguous and guaranteed to terminate after a finite number of applications.
  • A data structure is an arrangement of data or locations for data, organized especially to match the problem.
  • We’ve stated before that the best solution to a problem is the simplest adequate one; for any problem we should strive for the simplest approach.
  • In choosing which approach to apply towards solving a problem, give preference in the following order: calculation, data structures, logic.
  • In devising an algorithm, consider exceptions last. In writing code, handle exceptions first.
  • In Forth we try to minimize our dependent on logic.
  • If you have trouble thinking about a conceptual model, visualize it--or draw it--as a mechanical device.
  • Good organization has three aspects: decomposition, composition, disk partitioning.
  • Composition is the putting together of pieces to create a whole. Good composition requires as much artistry as good decomposition.
  • Structure your application listing like a book: hierarchically.
  • Modularity of the source code is one of the reasons for Forth’s quick turnaround time for editing, loading, and testing (necessary for the iterative approach).
  • Begin all definitions at the left edge of the screen, and define only one word per line.
  • Spacing and indentation are essential for readability.
  • Every colon or code definition that consumes and/or returns any arguments on the stack must include a stack-effect comment.
  • The purpose of commenting is to allow a reader of your code to easily determine what’s going on.
  • Design your application so that the code, not the comments, conveys the meaning.
  • The most-accurate, least-expensive documentation is self-documenting code.
  • Choose names according to “what”, not “how”.
  • Find the most expressive word.
  • Spell names in full.
  • Favor short words.
  • Hyphenated names may be a sign of bad factoring.
  • Don’t bundle numbers into names.
  • Use prefixes and suffixes to differentiate between like words rather than to cram details of meaning into the name itself.
  • Maintainability requires readability.
  • Factoring means organizing code into useful fragments.
  • Don’t pass control flags downward.
  • If a series of definitions contains identical functions, with variation only in data, use a defining word.
  • Keep definitions short.
  • A word should be a line long. That’s the target.
  • Factor at the point where you feel unsure about your code (where complexity approaches the conscious limit).
  • Factor at the point where a comment seems necessary.
  • Limit repetition of code.
  • When factoring out duplicate code, make sure the factored code serves a single purpose.
  • Look for repetition of patterns.
  • Be sure you can name what you factor.
  • If you have a concept that you can’t assign a single name to, not a hyphenated name, but a name, it’s not a well-formed concept. The ability to assign a name is a necessary part of decomposition.
  • Simplify the command interface by reducing the number of commands.
  • For maximum maintainability, limit redundancy even at compile time.
  • Work on only one aspect of a problem at a time.
  • By concentrating on one dimension of the problem at a time, you can solve each dimension more efficiently.
  • Don’t change too much at once.
  • Don’t try to anticipate ways to factor too early.
  • Building levels of abstraction is a dynamic process, not one you can predict.
  • Today, make it work. Tomorrow, optimize it.
  • A good programmer continually tries to balance the expense of building-in changeability against the expense of changing things later if necessary.
  • Anticipate things-that-may-change by organizing information, not by adding complexity. Add complexity only as necessary to make the current iteration work.
  • Forth handles data in one of two ways: either on the stack or in data structures.
  • The simplest way for Forth words to pass arguments to each other is via the stack.
  • Simplify code by using the stack. But don’t stack too deeply within any single definition. Redesign, or, as a last resort, use a named variable.
  • Generally, three elements on the stack is the most you can manage within a single definition.
  • Especially in the design phase, keep on the stack only the arguments you’re using immediately. Create local variables for any others. (If necessary, eliminate the variables during the optimization phase.)
  • When determining which arguments to handle via data structures rather than via the stack, chose the arguments that are the more permanent or that represent a current state.
  • Make sure that stack effects balance out under all possible control flows.
  • When doing two things with the same number, perform the function that will go underneath first.
  • Where possible, keep the number of return arguments the same in all possible cases.
  • Keep return stack operators symmetrical.
  • Unless it involves cluttering up the stack to the point of unreadability, try to pass arguments via the stack rather than pulling them out of variables.
  • Most of the modularity of Forth comes from designing and treating Forth words as “functions” in the mathematical sense.
  • The return stack is a component of the Forth system designed to hold return addresses, and thereby serve as an indication of where you’ve been and where you’re going.
  • When the application requires handling a group of conditions simultaneously, use a state table, not seperate variables.
  • A CONSTANT’s value should never be changed once the application is compiled.
  • Using the stack is preferable for testing and reusability, but too many values manipulated on the stack by a single definition hurts readability and writability.
  • Control structures aren’t as important in Forth as they are in other languages.
  • The use of conditional structures adds complexity to your code.
  • The more complex your code is, the harder it will be for you to read and to maintain.
  • Give each function its own definition.
  • The Forth dictionary is a giant string case statement. The match and execute functions are hidden within the Forth system.
  • Don’t test for something that has already been excluded.
  • When multiple conditions have dissimilar weights (in likelihood or calculation time) nest conditionals with the term that is least likely to be true or easiest to calculate on the outside.
  • The most elegant code is that which most closely matches the problem. Choose the control structure that most closely matches the control-flow problem.
  • Don’t decide, calculate.
  • Many times conditional control structures are applied mistakenly to situations in which the difference in outcome results from a difference in numbers.
  • A trick is simply taking advantage of certain properties of operation.
  • The use of tricks becomes dangerous when a trick depends on something likely to change, or when the thing it depends on is not protected by information hiding.
  • Use decision tables.
  • A decision table is clearly a better choice than a conditional structure when the problem has multiple dimensions.
  • One change at the bottom can save ten decisions at the top.
  • Don’t test for something that can’t possible happen.
  • Reexamine the algorithm.
  • A lot of conditionals arise from fuzzy thinking about the problem.
  • Avoid the need for special handling.
  • Use the structured exit.
  • Fooling with the return stack is like playing with fire. You can get burned.
  • Take the action when you know you need to, not later.
  • Don’t put off till run time what you can compile today.
  • Any time you can make a decision prior to compiling an application, do.
  • The use of logic and conditionals as a significant structural element in programming leads to overly-complicated, difficult-to-maintain, and inefficient code.
  • Most people go out and attack problems with complicated tools. But simpler tools are available and more useful.
  • Forth is expressed in words (and numbers) and is separated by spaces.
  • All words, whether included with the system or user-defined, exist in the “dictionary”, a linked list.
  • Writing a Forth application consists of building increasingly powerful definitions in terms of previously defined ones.

No comments:

Post a Comment