Artifact [2ed874ebbf]

Artifact 2ed874ebbfa6b83bdfe5ad157ae2bf95055575bd12cfa0728f0b5098672a88d7:


Ascetic Programming

Samuel A. Falvo II

kc5tja@arrl.net

2018 Aug 25

Presenter Notes

I've been thinking about Ascetic Programming again, after reviewing my Equilibrium sources again. I still maintain that it's got potential to facilitate writing surprisingly sophisticated Forth programs without resorting to object orientation.

Not that there's anything wrong with object orientation per se; I just find it makes using Forth somewhat alien in general case. I find Ascetic Programming feels more natural to me.


DEFINITION

Ascetic:

Characterized by or suggesting the practice of severe self-discipline and abstention from all forms of indulgence.

Presenter Notes

  • Here's the definition of ascetic... There are two aspects of this definition that I'd like to point out.

  • First, on the topic of self-discipline.

    • Forth programming always involves a ton of self-discipline.
    • How many times have you willingly written code to store 0 to address 0?
    • Forth, even more than C, will happily let you hang yourself this way.
    • It does absolutely nothing to prevent you from doing stuff like this.
  • Discipline becomes even more important when program defects are not so obvious as this.

    • How many times have you been in the middle of an intense development session, only to have your dictionary get corrupted?
    • This happens quite frequently even in an environment such as GForth,
    • one of the most heavily "memory protected" Forth environments I'm aware of.
    • It's not a matter of HOW the dictionary got corrupted;
    • rather, it's a matter of WHY you let it happen in the first place.
    • Forth's just following orders, after all.
    • Ensuring that this never happens is your responsibility, and only your responsibility.
    • You, therefore, must have sufficient self-discipline to ensure all pointers are valid,
    • or you risk insanity when trying to debug.
  • The second thing I'd like to point out about the definition is the focus on indulgences.

    • Two forms of indulgence exists: language and run-time.
    • Run-time
      • What is OOP?
      • It's a way of thinking about how components of a program interact with each other.
      • you essentially anthropomorphize how software works.
      • You have these units of software which conceptually are operating independently from each other, and which communicate amongst each other using the computer equivalent of virtual photons: messages.
      • Read what Alan Kay has to say about what he was thinking when he coined "Object Oriented".
    • Language
      • One of the side effects of this anthropomorphization of code,
      • you find terms such as "is-a", "has-a", and other concepts which make sense only in the context where one object possesses another object.
      • That is, it has exclusive control over the object, or which acts as a gate-keeper for it.
      • This happens frequently; read the book "Object Oriented Design Patterns" by the gang-of-four to see many, many examples of this.
  • So, you have several kinds of indulgences here:

  • on the one hand, objects can have possessions which leads to all manner of control issues especially in multithreaded applications,

  • and on the other hand, OOP frequently requires extending the Forth language itself to support the basic concepts.

  • Ascetic programming does away with all these indulgences.


DEFINITION

Ascetic Programming:

Writing modular, extensible, easy to maintain software which avoids the use of objects, and relying as great as possible on early-bound Forth semantics.

Presenter Notes

  • Here's my definition of ascetic programming. Note that it covers both of the indulgences discussed earlier.
  • We dispense with object orientation
    • Eliminates the most containment/ownership issues
      • Who owns the memory? Garbage collection? RAII? Nice to have, but unnecessary.
      • Law of Demeter, Single Responsibility Principle, SOLID, and other rules of thumb are automatic.
    • Eliminates message passing metaphor.
      • Relies on relations: facts are recorded and maintained in its place.
      • Early-binding, not late-binding, allows for significantly easier time debugging when things go wrong.
    • Sometimes, containment and late-binding is advantageous
      • Made explicit in the code where this occurs.
  • We dispense with language enhancements
    • Which syntax, exactly? FIG-Forth? Forth-79? ANS Forth? Forth-2012?
    • Minimizes dependencies
      • Easier to publish code
      • Easier to reason about the code
      • Eliminates duplication of intent in code
        • OOF code calling Wil Baden's OOP code, which calls SwiftForth OOP code, etc.
  • We rely on self-discipline much more heavily than other programming approaches.
    • I rely on Declarative, Inquisitive, then Imperative programming conventions to habituate this discipline into the subconscious.
    • With only a small amount of practic,e you'll find writing this way becomes largely automatic.
    • Per Chuck Moore: Things which can happen earlier in the development cycle should happen earlier. Edit-time, Compile-time, then Run-time, in order of preference.
  • Smaller, simpler, faster, easier to debug, and easier to write. These are all significant wins which should not be underrated.

Concepts

  • Entities replaces objects.
    • Identity, but no address.
  • entity ( -- n ) allocates a new entity ID.
  • retired ( n -- ) frees an entity ID.

Presenter Notes

  • An entity
    • has a unique identity, but not a unique address in memory.
    • will be used frequently as a foreign key in many data structures.
  • retired may or may not actually do anything. For a 16-bit Forth, it's wise to re-use IDs. For a 64-bit Forth, you'll die of old age before you run into a case where all 7.5 billion people on Earth allocates entity IDs such that it wraps around.

Concepts

  • Relations replaces:
    • typing
    • containment
    • more?
  • Relations are strategic, not tactical.

Presenter Notes

  • Once you can identify entities, you can record facts about them in various tables.
    • NOTE: table is conceptual here; maybe you're storing data in a tree instead. Whatever; details aren't important at this high-level overview.
  • Relationships implies set-membership, which is more expressive than any type system, static or dynamic.
  • Most uses of containment in object oriented programming is to maintain a close relationship with the contained object.
    • Model/View/Controller -- when a model changes, the model has to update all views on that model. Controller needs to know what the model is. Etc.
  • Strategic code is problem-oriented; tactical code is boilerplate.
    • Experience: only about 20% of classes in OOP programs are problem-oriented. 80% are factories, configuration management, or other boilerplate needed to lubricate the smooth functioning of the remaining 20%.

Demo and Code Review

Imagine a simple game of Connect-Four.

  • It's hard to play a game without an interactive interface.
  • A Game Board, which consists of 7 columns of 6 rows.
  • Score indicators
  • Up to 42 checkers
  • Human player
  • Computer player
  • Mostly early-bound, but some late-bound behaviors!

These are independent things which may or may not be visible at any given time.


Modules

  • Responsible only for its own memory management.
  • Public vs Private Interface
    • Generally, private words are imperative in nature.
    • Generally, public words are inquisitive or declarative in nature.
    • Public interface always idempotent.

Example:

!forth
create database  /database allot
variable #records
    . . .
: appear ( e - )          ... ;
: (invis) ( e r# - e r# ) 2dup cells database + @ = 
                          if 2drop rdrop -1 then ;
: visible? ( e - f )      0 begin dup #records @ < while
                          (invis) 1+ repeat 2drop 0 ;
: -vis ( e - e )          dup visible? if drop rdrop then ;
: visible ( e -- )        -vis appear ;

Presenter Notes

  • Even from this nonsensical example, some things become apparent:
    • visible is side-effecting, but is also idempotent. I can run it once, or a hundred times; the outcome will remain the same.
    • Most or even all declarative words have corresponding inquisitive words as part of their implementation. CRUD-like interfaces are easy and largely automatic.
    • Internal definitions have shorter, more mnemonic than self-documenting, names. These are generally OK to shadow in subsequent modules.
      • Internal words frequently have sophisticated control flow (R-stack manipulation).
      • Notice how public interface words hide these details from the caller though.
    • This module defines its own data store for maintaining state. Words like appear and (invis) know how to manage this memory.
    • Modules are entirely self-contained. They don't even know how entities are represented (except insofar as that they are represented in a single cell on the stack).
    • DItI is not required, but it sure does help. As indicated above, if you know DItI's rules, you can reason about each definition in complete and total isolation from even its dependencies.
  • My examples use statically allocated buffers. However, this is an implementation detail. Your modules are free to use dynamically allocated memory if you want. B-trees, skip-lists, hash tables, etc. It's all up to you.

Polymorphism

  • Visible objects are great, but what do they look like?
  • Objects are a good abstraction for user interfaces, because you can ascribe shapes to classes of objects.
    • Buttons, menus, players, bullets, spreadsheet cells, etc.
  • Whether an object is visible/drawn or not is a declarative matter; ideally expressed using ascetic techniques.
  • Object-specific behavior is best addressed using imperative methods or callbacks; best expressed using late-bound techniques.

Let's update our visible interface to accommodate this fact:

!forth
: visible ( xtDraw e -- )  -vis +row dup ent! swap xtDraw! appear ;

IF it's not already visible AND there's room for another row in the database THEN record the entity and draw procedure in the table, then make it appear.


Summary

  • Principles
    • Modules are solely responsible for managing their own memory. No exceptions.
    • Modules encapsulate knowledge (facts as they pertain to one or more entities).
    • Entities have identity, but not proximity.
    • Non-polymorphic public interfaces are declarative and inquisitive in nature.
    • Polymorphic interfaces (e.g., callbacks) generally tend to be imperative in nature.

Summary

  • Cavaets
    • Ascetic programming is not fast programming. Keep a dictionary and thesaurus handy, as both Moore and Knuth recommended.
    • Ascetic programs are not fast programs. You don't have the luxury of following pointers to get to relevant data.
    • BUT, ascetic programming is fast enough for most needs, especially if you are more clever about your module's storage model.

Summary

Just as Test-Driven Development enables Extreme Programming, so too does Declarative, Inquisitive, then Imperative enable Ascetic Programming.


FIN.

Thank you for your attention and questions.