The KISS definition in wikipedia is: “an acronym for “keep it simple stupid” or “keep it stupid simple”, is a design principle noted by the U.S. Navy in 1960. The KISS principle states that most systems work best if they are kept simple rather than made complicated; therefore, simplicity should be a key goal in design, and unnecessary complexity should be avoided”. This article summrizes its meaning in functional programming.

Simple Made Easy

Simple Made Easy emphasizes simplicity over easiness. The origin of “simple` is “sim-plex”: one fold/braid. The opposite is complex: fold/braid together. The origin of “easy” is “adjacent”: nearby. The opposite is hard: far. So simple means: one role, one task, one concept, one dimension, but not one instance or one operation. It is about lack of interleaving, not cardinality. It is objective. Easy is near, at hand, familiar: near our capabilities. Easy is relative.

Constructs are dev tools used, it helps the programmer convienience and programmer replaceability. The long term results of artifacts/use are software quality, correctness, maintenance, change. You have to understand the problem to make the solution reliable. Emphasizing ease gives early speed but complexity will slow you down over the long haul.

Many constructs are easy but complex. The generated artifacts are hard to understand and unreliable. The make things easy: install to bring to hand, learn to become familiar. Don’t complect (interleave, entwine, braid) things. Compose simple components.

Complexity Simpicity
State, Object Values
Methods Functions, Namespaces
vars managed refs
inheritance, switch, matching polymorphism a la carte
syntax data
imperative loops, fold Set functions
Actors Queues
ORM delcarative data manipulation
conditions rules
inconsistency consistency

Partitioning and stratification don’t imply simplicity, but enable it. The complex constructs are:

Construct Complects
State value and time, everything that it touchs
Objects State, identity, value
Methods Function and state, namesapces
Syntax Meaning, order
Inheritance Types
Switch/matching Multiple who/what pair
Vars Value and time
Imperative loops, fold What and how
Actors What and who
ORM OMG
Conditionals Why, rest of program

The simple contructs are:

Construct Get it via
Values Final, persistenct collection
Functions Stateless
Namespace Language support
Data Maps, arrays, sets, XML, JSON etc
Polymorphism a la carte Protocols, type classes
Managed refs Composing value and time
Set functions Libraries
Queues Libraries
Delcarative data manipulation SQL/LINQ/Datalog
Rules Libraries, Prolog
Consistency Transactions, values

How to developme simple systems:

  • Abstraction for simplicity: it not hiding, it about abstract who/what/when/where/why/how
  • What: a small set of functions and a spcification of inputs, outputs and semantics. Use polymorphism contructs. Don’t complect with how.
  • Who: entities implement abstractions. Build from subcomponents in direct-injection style.
  • How: implementing logic, connect to abstractions and entities via polymorphism constructs.
  • When/Where: use queues to avoid complecting these with anything in the design.
  • Why: the policy and rules of the application. Explore rules and declarative logic systems.

Suggestions:

  • Information is simple, represent data as data, do not hide it behind a micro language and methods.
  • Choose simple constructs over complexity-generating constructs.
  • It is the artifact matters, not the ahuthoring.
  • Simplify the problem space before you start.
  • Simplicity often means more things, not fewer.

But Not Simpler

Some algebraic axes from The Axes of Abstraction:

  • Associativity: Category Theory
  • Commutativity: CRDT
  • Distributivity: belief propagation
  • Idempotency: lattices
  • Invertibility: isomorphism

Generalization example from the algebra:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def f(x: 5): 10 = x + x   // it is 10
def f(x: Int): Int = x + x     // don't assume 5
def f[A <: Number](x: A): A = x + x   // don't assume integer
def f[A: CommutativeRing](x: A): A = x + x  // don't assume number
def f[A: Ring](x: A): A = x + x    // don't assume * is commutative
def f[A: Rng](x: A): A = x + x    // don't assume * has identity
def f[A: CommutativeGroup](x: A): A = x + x // don't assume * exists
def f[A: Group](x: A): A = x + x  // don't assume + is commutative
def f[A: Monoid](x: A): A = x + x // don't assume + has an inverse
def f[A: Semigroup](x: A): A = x + x  // don't assume + has identity
def f[A: Magam](x: A): A = x + x // don't assume + is associative
def f[A](x: A): A  = x // Assume nothing

The Lawful Asynchronous Programming lists the benefits of math laws:

  • refactor and change code safely
  • allow to ignore deatils and focus on abstraction
  • simplify reasoning of life cycles, finalizers and error handling
  • compose components easily

This talk gives lessons learned from the failure of Scalaz Stream streaming I/O library: it starts with clever algebra, neat ideas but ends with undesirable outcomes: no associativity in append and finalizer. One should constrains the generality to make it simple.

The Scalazzi Subset

To write good functional programs, you need to follow the functional laws, especially the referential transparency. The Scalazzi Subset doc defines it as: “an expression expr if referentially transpraent if in all program p, all occurences of expr in p can be replaced by the result assigned to expr without causing an observable effect on p”.

The safe Scala Subset is defined as to not use the following

  • null
  • exception
  • Side effects
  • Type casing (isInstanceOf)
  • type casting (asInstanceOf)
  • equals/toString/hashCode
  • notify/wait
  • classOf/getClass

The Haoyi’s blog Strategic Scala Style: Practical Type Safety has a good explanation about the type safety.