On the way back from RC I thought about the challenges I will have to face for Cerebro in the next week:
- only mutate sources that are covered by tests (even if for now I could assume 100% code coverage)
- improve the usage of data structures, right now there's a lot of impedence mismatch between modules
- solve the duplicated variable declaration
It was really productive, I've also had a couple ideas about Professor X but that's way too early.
I think I have to setup some kind of daily reminder of good activities, something like:
during the week
- cerebro
- project lamo
- study groups
during the weekend
- mock interviews
- catchup with backlog
- the little schemer (and hopefully at some point SICP)
Started implementing rember
as specified. It's taking quite some time.
I've had a look at this video from LambdaConf 2016, it's helpful if you want to understand where Functional Relational Programming concepts are applied in today's UI frameworks.
DISCLAIMER: read the paper, don't read my notes. These are here for my benefit.
A primary value of that paper, which I highly recommend, is the focus on complexity, and the role of state in complexity.
-- Rich Hickey (creator of Clojure)
The classic approach to solving the problem represented by state is either:
- enclosing state in objects using object oriented programming
- or using functional programming
Complexity as the major problem in software, due to state handling.
Goal is being able to think and reason about our systems.
Testing from the outside and informal reasoning (looking at the inside) as a way to understand a system are both faulty.
-
testing doesn't help because it covers just one possible state and you know nothing about what would happen with a different state
-
informal reasoning doesn't help because its difficult to hold in mind all the possible scenarios that are happing as consequence of dealing with state
Order and concurrency can add even more uncertainty to informal reasoning
What would you pick you had to choose between
- testing
- reasoning
- simplicity
Simplicity. Because any future attempt to understand the system will work.
The following are common answers when dealing with a faulty piece of software that is not working and we need to have it in a working condition:
- try it again
- reload the document
- restart the program
- reboot your computer
- re-install the program
- re-install the operating system and then the program
These are all caused by state, and are all remedies trying to put the internal state back into a good situation.
It's really hard to enumerate and understand all possible states of a system
Control, intended as ordering of statements, is another source of complexity, as is code volume.
So: complexity comes from, in order of relevance: state, control, and volume
By suitable application of our powers of abstraction, the intellectual effort needed to conceive or to understand a program need not grow more than proportional to program length.
Complexity breeds complexity and it poisons adjacent parts.
Simplicity is hard.
Power corrupts: state is power, manual memory management is power (hence it's being removed).
Enforce integrity contraints over state by regulating access to state via methods
Problems:
- contraints are at single object level
- multiple resource might ask the same thing at the same time updating it
OOP relies on state and all behaviour is related to this state, so it's not a good foundation for avoiding complexity.
By avoiding state and side effects the entire system gains referential transparency property given a set of arguments the function will always behave in the same way
// no! state! bad!
let getNextCounter = () => ++counter
// while in FP you would
let getNextCounter = (oldCounter) => oldCounter + 1
The caller should make sure getNextCounter
is called with the right param, passing a big object for
example named state
that contains all state lets us keep referential transparency but at the cost
of ease of reasoning.
Functional program: you can always tell what will come out of a procedure by looking at its parameters
Stateful program -> nope.
Logic program -> removes the burden of control
Essential - intrinsic of the problem
Accidental - all the rest (infrastructure issues, performance issues, language issues, etc)
Try to avoid as much accidental complexity as possible
Recommendation - complexity (state + control) 1: avoid, 2: separate
Declare accidental state in a completely separated infrastructure, we can then forget that accidental state even exists
Split logic from state to get
→ essential logic
accidental state
and control ↓
→ essential state
- structuring data - relations as means for representing data
- manipulating that data - a means to specify derived data
- maintaining integrity and consistency of state - inviolable restrictions on data
- separation between logical and physical layers - concerns of logical model and efficient physical storage model designs are addressed separately (that is called data independence)
- base
- derived - composed of other relations (base or derived)
No duplicates and no order
No need to come up with access paths beforehand, think of employees containing departments or departments containing employees (or even worse, both) as a problem that relations solve
Manipulating happens through the use of relational algebra:
- restrict(r) - selection of a subset of the records in a relation according to some criteria
- project(r) - new relation from an old one without some attributes removed from the records
- product(r1, r2) - carthesian product
- union(r1, r2) - creates a relation of all items from both argument relations
- intersection(r1, r2) - creates a relations containing all items in both argument relations
- difference(r1, r2) - create a relation containing all records in r1 but not in r2
- join(r1, r2) - creates all possible records that result from matching identical attributes in r1 and r2
- divide(r1, r2, r3) - returns all records of r1 occurring in r2 associated with each record in r3
Maintained by specifying constraints that must hold at all times.
Separating the logical model from the physical storage representation.
In FRP all essental state takes the form of relations, and the essential logic is expressed using relational algebra extended with pure user defined functions
FRP goal is eliminate complexity.
- relational model specifies essential state
- relational algebra specifies essential logic
- observers listen on a relationship to update UI and outputs
- accidental complexity could be specified declaratively
- Link to the whitepaper
- David Nolen (creator of ClojureScript) wrote logic tutorial
- Something that's about to be born
- videos from Chomsky - find which ones are the best and get them
- "check that parens are balanced" exercise
- still need some attention needed for timsort, radix sort
- chapter 3 of the little schemer in Clojure