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.