notes-computer-programming-programmingLanguageDesign-simpleMadeEasy

(this talk is not only about programming language design, that's just where i've categorized it)

https://www.infoq.com/presentations/Simple-Made-Easy

(note: the "notes" in "Show notes" on the InfoQ? website are not exactly what was in the talk (in either/both of the spoken words or the slides); an example of adding something which was not in the talk: an example of non-comprehensiveness: the slide at 0:39:28, The Complexity Toolkit, corresponds to InfoQ?'s first note under the heading 'Constructs that generate complex artifacts', but the actual slide also has 'Conditionals: why, rest of program' whereas InfoQ?'s note does not note a 'Conditional' item at all; still, the InfoQ? notes are pretty good)

an accurate transcript appears to be online at:

https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/SimpleMadeEasy.md


summary notes

defns

defn SIMPLE, COMPLICATED, COMPLECT

define simple as "one fold/braid" or "lack of interleaving", for example: only serve one role, accomplish one objective, be about one concept.

but note that this is about lack of interleaving, not cardinality; "it doesn't mean that there's only one of them. It also doesn't mean an interface that only has one operation. " "simplicity often means making more things, not fewer" "I'd rather have more things hanging nice, straight down, not twisted together, than just a couple of things tied in a knot.

simplicity is objective; you can objectively tell whether two things are braided together or not.

COMPLICATED is the opposite of SIMPLE

COMPLECT is 'to fold/braid/interleave', that is, 'to make complicated'.

My note: let's just use 'COUPLED' instead of 'COMPLICATED' if this is what e means, b/c ppl are already using that in the software context. So his simple = 'uncoupled' or 'loosely coupled', his 'complicated' = 'coupled' or 'tightly coupled', his 'to complect' = 'to couple'. E also uses the word 'entanglement'.

"You have to start developing sensibilities around entanglement. That's what you have to -- you have to have entanglement radar"

My note: people would say that a programming language with a million keywords, or a program with a million moving parts, is not 'simple' even if all of those constructs were 'unbraided' with each other; more formally we can consider the amount of information contained in something, that is, how many characters it would take to write it all down. So his definition is just one aspect of how the word 'simple' is used, and there are still some other aspects missing even after you consider his definition of 'easy'.

defn EASY

define easy as (1) near at hand ("can I get this instantly and start running it in five seconds?") or (2) familiar, or (3) near our capabilities

easy is subjective; something can be near-at-hand/familiar/near-the-capabilities for one person but not for a different person; "I can't read German. Does that mean German is unreadable?".

defn abstract

sometimes 'to abstract' is used to meanhiding stuff (eg hiding complexity) but it's important to keep in mind that this is not its ONLY meaning, rather, more generally it means to draw away from the physical nature of something.

misc notes

we're mostly talking about whether the resulting program is simple, NOT whether the experience of writing that program was simple

What really matters is not if a construct provides a good programming experience, but rather, if its usage yields simpler artifacts (that is, if it leads you to produce simpler programs). "some things that are easy actually are complex. There are a bunch of constructs that have complex artifacts that are very succinctly described...Some of the things that are really dangerous to use are like so simple to describe"

however one feels "about the shape of the code they type in is independent from this and this is the thing you have to live with."

the value of simplicity

"Simplicity is a prerequisite for reliability". - Edsger W. Dijkstra

It also improves your ability to change the system.

Coupling limits "our ability to understand our systems" because we can only keep a small number of things in mind at one time. "when things are intertwined together, we lose the ability to take them in isolation....if every time...I pull out a new part of the software I need to comprehend, and it's attached to another thing, I had to pull that other thing into my mind because I can't think about the one without the other."

vs. easy:

EASY1 and EASY2 are often overprioritized -- SIMPLE should be given more weight.

Simplicity requires slower speed of development early on, because you have to spend time upfront simplifying the problem space. But although EASY1 and EASY2 give us higher development speed at the beginning, in the longterm it's SIMPLE which saves us more time.

(funny analogy: "What kind of runner can run as fast as they possibly can from the very start of a race?...only somebody who runs really short races... But of course, we are programmers, and we are smarter than runners, apparently, because we know how to fix that problem, right? We just fire the starting pistol every hundred yards and call it a new sprint.")

testing and type systems are not sufficient

You still need simplicity.

modularity is not sufficient

Even if things are separated into modules, they could still be coupled, because the design of one of the modules may be making assumptions about another modules.

To tease this out, ask if one modules is being allowed to 'think about' another module (bad), or if it is only being allowed to 'think about' an abstraction of the other module (i assume e's referring to 'interfaces' here? mb e doesn't use that term b/c e later distinguishes between 'interfaces' and protocols/typeclasses)

environmental complexity

stuff like resources, memory, CPU. Inherent in implementation space. e doesn't know a good way to simplify this (you can do stuff like preallocate memory, segment memory, but that's wasteful). The main problem is that the policies around this stuff don't compose; eg you can't say 'i'll just size my threadpool to the number of cores' many times in the same program.

information is simple, don't ruin it

"information: it is simple. The only thing you can possibly do with information is ruin it. :) "

"

" Objects were made to encapsulate IO devices, so there's a screen, but I can't touch the screen, so I have the object...That's all they're good for. They were never supposed to be applied to information. And we apply them to information that's just wrong....It's wrong because it's complex...it ruins your ability to build generic data manipulation things. If you leave data alone, you can build things once that manipulate data, and you can reuse them all over the place, and you know they're right once and you're done.

The other thing about it, which also applies to ORM is that it will tie your logic to representational things, which again tying, complecting, intertwining. So represent data is data. Please start using maps and sets directly. Don't feel like I have to write a class now because I have a new piece of information. That's just silly. "

specific constructs

MORE COMPLICATED vs LESS COMPLICATED:

There are reasons why you might have to use the more complicated things, but boy, there's no reason why you shouldn't try to start with this less complicated ones.

State vs. Values

Why is state complicated? by definition, it couples value with time (note, this is true even with a non-concurrent program). State couples 'everything that touches it'. Related, when you debug a stateful program, you first have to try to reproduce the state that the program was in when the user encoutered the bug, which can be difficult.

How to get values in your language: most languages have something like 'final' or 'val'; in some languages it's harder to find aggregate values; you want to look for 'persistent collections'

Objects vs values

Objects couple state, identity, value.

(see also Information is Simple, above)

Methods vs. Functions, Namespaces

Methods couple function and state, and, in some languages, also namespaces.

why namespaces?:

How to get functions in your language: most languages have functions. "If you don't know what they are, they're like stateless methods :) "

How to get namespaces in your language: unfortunately this requires language support ("something you really need the language to do for you, and unfortunately it's not done very well in a lot of places")

vars vs. Managed refs

(although Managed refs are also complex)

refs and vars are both good because they mean that the language is stateless by default and require you to label where you have state.

Clojure and Haskell's references are superior though, because they "compose values and time". These constructs do 2 things: allow you to extract a value, and provide an abstraction of time.

"That's really important, because that's your path back to simplicity; if I have a way to get out of this thing and get a value out, i can continue with my program. ((but, by contrast,)) If I have to pass that variable to somebody else or a reference to something...i'm ((contaminating)) the rest of my system"

"We saw a picture during a keynote yesterday of this amazing memory where you could dereference an address and get an object out -- I want to get one of those computers! The only thing you can ever get out of a memory address is a word, a scalar... recovering a composite object from an address is not something that computers do...variables have the same problem"

How to get managed refs in your language: Clojure/Haskell refs

Inheritance, switch, matching vs. Polymorphism a la carte

Inheritance, by definition, couples types with other types.

Switch or matching couples "Multiple who/what pairs"; they couple "multiple pairs of who's doing to do something, and what happens... and they do it all in one place, in a closed way -- that's very bad" .

e emphasizes that Polymorphism a la carte is very important.

e later distinguishes between 'interfaces' and protocols/typeclasses and implies that protocols/typeclasses are better than interfaces; presumably e would say that (Java) interfaces are polymorphism but not quite 'a la carte'? Perhaps the difference is that a third-party can provide a protocol-extension/typeclass-implementation for a type, whereas (i think) in Java a class must declare its support for an interface in the class definition?

How to get polymorphism a la carte in your language: clojure protocols, haskell type classes

Syntax vs. Data

Syntax couples meaning and order.

"syntax...it's...inferior to data in every way"

"Data is actually really simple; there's not a tremendous number of variations in the essential nature of data. There are maps, there are sets, there are linear sequential things." but "We create hundreds of thousands of variations that have nothing to do with the essence of the stuff", "constructs we put around...and globbed on top of data"

also, "same thing for communications; are we all not glad we don't use the unix method of communicating on the web? Any arbitrary command string can be the argument list for your program and any arbitrary set of characters can come out the other end. ((sarcastic)) Let's all write parsers!"

(see also 'information is simple' above)

Imperative loops, fold vs. Set functions

Imperative loops couple 'what' with 'how'. Folds couple the things being folded with their ordering(?)

How to get set functions in your language: libraries

Actors vs. Queues

Actors couple 'what' with 'who'

How to get queues in your language: libraries

ORM vs. Declarative data manipulation

ORMs couple a whole bunch of things. (see also Information is Simple, above)

How to get declarative data manipulation in your language: SQL/LINQ/Datalog

Conditionals vs. Rules

Conditionals "have a bunch of rules about what our program's supposed to do strewn throughout the program. Can we fix that?" "Instead of embedding a bunch of conditionals in our raw language at every point of decision, it's nice to sort of gather that stuff and put it over some place else"

How to get conditionals in your language: libraries or Prolog

Inconsistency vs. Consistency

e means consistency in the concurrency/memory ordering consistency/distributed systems sense, not in the sense of a consistency of a design. Eg e mentions 'eventual consistency' as a form of inconsistency.

How to get consistency in your language: transactions/values

a note on paren syntax

" Is a language built all out of parens simple... is it free of interleaving and braiding? And the answer is no; Common Lisp and Scheme are not simple is this sense, in their use of parens. Because the use of parentheses in those languages is overloaded; parens wrap calls, they wrap grouping, they wrap data structures; and that overloading is a form of complexity... We can fix that; we can just add another data structure, it doesn't make Lisp not Lisp to have more data structures. It's still a language defined in terms of its own data structures, but having more datastructures in play means that we can get rid of this overloading in this case... " -- Rich Hickey, https://www.infoq.com/presentations/Simple-Made-Easy 0:26:03. Note: the slides say more specifically how Clojure addresses this by adding another data structure; they say, "Adding a data structure for grouping, e.g. vectors, makes each simpler"

who, what, when, where, why, and how

one way to take things apart: who, what, when, where, why, and how.

maintain the approach "i don't know, i don't want to know"

"If you go in saying this is a job, and I've done who, what, when, where, why, and I found five components, don't feel bad. That's great... split out policy and stuff like that....When you're building the definition of a thing from subcomponents...the thing that...you want to avoid... is any of those kind of yellow thinking about blue, blue thinking about yellow kind of hidden detail dependencies" (by "yellow thinking about blue, blue thinking about yellow" e's referring to an example e gave in the talk at 0:34:10)

WHAT

WHAT = operations; what we want to accomplish

Form abstact specifications of functions: take a SMALL set of related functions and give it a name (eg by using language facilities such as interfaces, protocols, type classes).

Watch out for accidental coupling via "having some implication of the semantics of the function dictate how it is done. Fold is an example of that."

" The point I'd like to get across today is just that they should be really small, much smaller than what we typically see. Java interfaces are huge, and the reason why they are huge is because Java doesn't have union types, so it's inconvenient to say this function takes, you know, something that does this and that and that...And the thing with those giant interfaces is that it's a lot harder to break up those programs, so you're going to represent them with your polymorphism constructs. "

WHO

WHO = data or entities

Build from subcomponents in a modular fashion (eg as much as possible, take the subcomponents as arguments).

You want small interfaces, but you want many subcomponents.

Don't couple the component to the details of its (sub?)components, or to other entities.

HOW

HOW = how things happen, this is the actual implementation code, the work of doing the job

As much as possible, HOW should not be coupled with anything else.

Only connect 'to abstractions and entities" using polymorphism constructs, not e.g. switch or pattern matching. Then you have an "polymorphism policy, and that is really powerful, especially if it's runtime open".

"...again, beware of abstractions that dictate how in some subtle way". Prefer declarative tools.

WHEN, WHERE

Strenuously avoid coupling WHEN, WHERE with anything.

" I see it accidentally coming in mostly when people design systems with directly connected objects. So if you know your program is architected such that this thing deals with the input and then this thing has to do the next part of the job... if thing A calls thing B, you just complected ((coupled)) it....And now you have a when and where thing because now A has to know where B is in order to call B, and when that happens is whenever A does it. "

Instead, use queues. You should be using queues extensively.

WHY

WHY = the policy and rules of the application

Often strewn everywhere (in conditionals, coupled with control flow etc). Instead, explore rules and declarative logic systems.

" We typically put this stuff all over our application. Now if you ever have to talk to a customer about what the application does, it's really difficult to sit with them in source code and look at it.

Now, if you have one of these pretend testing systems that lets you write English strings so the customer can look at that, that's just silly. You should have code that does the work that somebody can look at ((my note: i think e means that the code that does the work should itself be the sort of thing that somebody can look at))...try to put this stuff someplace outside. Try to find a declarative system or a rule system that lets you do this work. "



(you probably don't want to read any further than this)

my notes below include some amount of my own interpretation, although i tried not to have too much; i tried to limit it to rewording, but some of my rewording assumes that some of my interpretation of his talk was correct. Also, there is no attempt to be comprehensive.



more detailed notes

SIMPLE

" one fold/braid ((eg:))

 but not:

about lack of interleaving, not cardinality

OBJECTIVE "

EASY " near, at hand on our hard drive, in our tool set, IDE, apt get, gem install

near to our understanding/skill set

near our capabilities

easy is RELATIVE "

e feels that we pay too much attention to "near at hand" and "familiar" at the expense of "simple". E feels that we could do well to pay more attention to "near our capabilities", and that in fact simplifying things helps to make things near our capabilities (see future slide Making Things Easy).

0:29:09

" What's in your Toolkit?

(in the talk, he notes: "I didn't label these things bad and good, I'm leaving your minds to do that :)" )

More complex vs simpler (in the original slides, the column names are 'Complexity' and 'Simplicity', but he notes in the talk that the 'Simplicity' column just means simplER):

"

complect: in 0:29:09. Hickey introduces an archiac word 'complect' whose definition is interleave, entwine, braid.

So all he means by that is the verb of making things complicated, but using the defintition of complicated as 'interleaved, entwined, braided'. My note: you could maybe also just use the word 'to couple' in place of 'to complect', as 'coupling' and 'tight coupling' are already widely used in the software context.

He notes that complect means 'to braid together' vs compose means 'to place together'.

33:38 Modularity and Simplicity

Having modular components is not sufficient for simplicity; you can have modular components and still be complex.

The issue is that there can still be all kinds of interconnections between modular components, even if they don't call each other (example: one component might assume that the other component will never return '17').

What we have to do is watch out what those modules are allowed to think about. They can't think about each other; they can only think about abstractions of each other (i assume he's referring to interfaces).

part of slide: "

0:35:40 State is Never Simple

Complects value and time.

part of slide: "

0:38:00

Not all refs/vars are Equal

(first he reiterates that none of these constructs make state simple; refs don't make state simple, just more simple)

refs and vars are both good because they mean that the language is stateless by default and require you to label where you have state

however e thinks that Clojure and Haskell's references are particulary superior in dealing with this issue, because they "compose values and time". These constructs do 2 things: allow you to extract a value, and provide an abstraction of time.

"That's really important, because that's your path back to simplicity; if I have a way to get out of this thing and get a value out, i can continue with my program. ((but, by contrast,)) If I have to pass that variable to somebody else or a reference to something...i'm ((contaminating)) the rest of my system"

0:39:28 The Complexity Toolkit

Let's see why ((various)) things are complex:

(the column headings here are 'construct' and 'complects', ie for each construct, the other column says what that construct complects; eg the first row could be read "State complects Everything that touches it")

43:50 The Simplicity Toolkit

columns: Construct, Get it via ((simpler construct)): "

"there are reasons why you might have to get off of this list, but boy, there's no reason why you shouldn't start with it"

0:47:22 Environmental complexity

stuff like resources, memory, CPU. Inherent in implementation space. He doesn't know a good way to simplify this (you can do stuff like preallocate memory, segment memory, but that's wasteful). The main problem is that the policies around this stuff don't compose; eg you can't say 'i'll just size my threadpool to the number of cores' many times in one program.

0:49:19

e notes that the word 'to abstract' does not ONLY mean hiding stuff (eg hiding complexity), rather it means to draw away from the physical nature of something.

one way to take things apart: who, what, when, where, why, and how.

maintain the approach "i don't know, i don't want to know"

0:50:38 WHAT

"what is what? What is operations, what is what we want to accomplish?

"

"We're going to form abstractions by taking functions and, more particularly, sets of functions and giving them names. In particular -- and you're going to use whatever your language lets you use. If you only have interfaces, you'll use that. If you have protocols or type classes, you'll use those. All those things are in the category of the things you use to make sets of functions that are going to be abstractions. And they're really sets of specifications of functions.

The point I'd like to get across today is just that they should be really small, much smaller than what we typically see. Java interfaces are huge, and the reason why they are huge is because Java doesn't have union types, so it's inconvenient to say this function takes, you know, something that does this and that and that...And the thing with those giant interfaces is that it's a lot harder to break up those programs, so you're going to represent them with your polymorphism constructs.

They are specifications, right? They're not actually the implementations. They should only use values and other abstractions in their definitions. So you're going to define interfaces or whatever, type classes, that only take interfaces and type classes or values and return them.

And the biggest problem you have when you're doing this part of design is if you complect this with how. You can complect it with how by jamming them together and saying here is just a concrete function instead of having an interface, or here's a concrete class instead of having an interface. You can also complect it with how more subtly by having some implication of the semantics of the function dictate how it is done. Fold is an example of that.

Strictly separating what from how is the key to making how somebody else's problem. If you've done this really well, you can pawn off the work of how on somebody else. You can say database engine, you figure out how to do this thing or, logic engine, you figure out how to search for this. I don't need to know.

WHO Who is about, like, data or entities. These are the things that our abstractions are going to be connected to eventually depending on how your technology works. You want to build components up from subcomponents in a sort of direct injection style. You don't want to, like, hardwire what the subcomponents are. You want to, as much as possible, take them as arguments...You should have probably many more subcomponents than you have, so you want really much smaller interfaces than you have, and you want to have more subcomponents than you probably are typically having because usually you have none.

If you go in saying this is a job, and I've done who, what, when, where, why, and I found five components, don't feel bad. That's great... split out policy and stuff like that....When you're building the definition of a thing from subcomponents...the thing that...you want to avoid... is any of those kind of yellow thinking about blue, blue thinking about yellow kind of hidden detail dependencies"

"

HOW

" How things happen, this is the actual implementation code, the work of doing the job.

You strictly want to connect these things together using those polymorphism constructs. That's the most powerful thing. Yeah, you can use a switch statement. You could use pattern matching. But it's glomming all this stuff together.

If you use one of these systems, you have an open polymorphism policy, and that is really powerful, especially if it's runtime open. But even if it's not, it's better than nothing.

And again, beware of abstractions that dictate how in some subtle way because, when you do that, you're really, you're nailing the person down the line who has to do the implementation. You're tying their hands. So the more declarative things are, the better, the better things work.

How is sort of the bottom. Don't mix this up with anything else. All these implementations should be islands as much as possible. "

0:54:39

WHEN, WHERE

" this is pretty simple.... you just have to strenuously avoid complecting this with anything.

I see it accidentally coming in mostly when people design systems with directly connected objects. So if you know your program is architected such that this thing deals with the input and then this thing has to do the next part of the job... if thing A calls thing B, you just complected it....And now you have a when and where thing because now A has to know where B is in order to call B, and when that happens is whenever A does it.

Stick a queue in there. Queues are the way to just get rid of this problem. If you're not using queues extensively, you should be. "

0:55:31 WHY

"

" We typically put this stuff all over our application. Now if you ever have to talk to a customer about what the application does, it's really difficult to sit with them in source code and look at it.

Now, if you have one of these pretend testing systems that lets you write English strings so the customer can look at that, that's just silly. You should have code that does the work that somebody can look at ((my note: i think e means that the code that does the work should itself be the sort of thing that somebody can look at))...try to put this stuff someplace outside. Try to find a declarative system or a rule system that lets you do this work. "

0:56:07 INFORMATION IS SIMPLE

"information: it is simple. The only thing you can possibly do with information is ruin it. :) "

"

" Objects were made to encapsulate IO devices, so there's a screen, but I can't touch the screen, so I have the object...That's all they're good for. They were never supposed to be applied to information. And we apply them to information that's just wrong....It's wrong because it's complex...it ruins your ability to build generic data manipulation things. If you leave data alone, you can build things once that manipulate data, and you can reuse them all over the place, and you know they're right once and you're done.

The other thing about it, which also applies to ORM is that it will tie your logic to representational things, which again tying, complecting, intertwining. So represent data is data. Please start using maps and sets directly. Don't feel like I have to write a class now because I have a new piece of information. That's just silly. "

"You have to start developing sensibilities around entanglement. That's what you have to -- you have to have entanglement radar"

testing and type systems are guard rails or safety nets, but they are not sufficient, you still need simplicity.

what really matters is not if a construct provides a good programming experience, but rather, if its usage yields simpler artifacts (that is, if it leads you to produce simpler programs). "some things that are easy actually are complex. There are a bunch of constructs that have complex artifacts that are very succinctly described...Some of the things that are really dangerous to use are like so simple to describe"

however one feels "about the shape of the code they type in is independent from this and this is the thing you have to live with."

simplicity requires slower speed of development early on in exchange for more speed later, because to gain simplicity, you have to spend time upfront simplifying the problem space.

"simplicity often means making more things, not fewer" "I'd rather have more things hanging nice, straight down, not twisted together, than just a couple of things tied in a knot. And the beautiful thing about making them separate is you'll have a lot more ability to change it, which is where I think the benefits lie."