CAUTION: https://github.com/attack-monkey/Lean-Functional-Typescript replaces this guide.
That's not to say that there are not some useful concepts covered in FLAT.
___________
| | .'. `````|`````
|______ | .''```. |
| | .' `. |
| |_______ .' `. |
Welcome to flat.
Flat emphasizes the simplification of logic in code by:
- Reducing mutations
- Reducing nested logic
- Reducing the number of returns from functions
Simple Flat is really a set of principles, and doesn't require any additional libraries. Reading this Readme and the chapter on Control Flow, will help you write much cleaner code.
Advanced Flat also:
- Uses Typescript which adds type-safety into your code!
- 🌶️ Uses The Prelude and a minimal set of other utility libraries to...
- Organise code into functions and pipes instead of object oriented hierarchies
Anytime you see a mention to The Prelude you'll see a 🌶️ at the start of the line. If there is a mention to another library, you'll see a 🧩.
Flat takes an fp (functional programming) over oo (object oriented) approach - so there is an emphasis on organising code into functions and pipes rather than oo based hierarchies.
While Flat takes on fp principles, it doesn't dive anywhere near as deep. Flat is primarily concerned with making flat untangled, well organised and safe code.
Almost everything in your code should be immutable...
// basic immutable addition
const a = 1
const b = a + 1
// adding keys to objects
const c = { cat: 'charlie' }
const d = { ...c, dog: 'xander' }
// adding array items
const e = [1,2,3]
const f = [ ...e, 4,5,6] // [1,2,3,4,5,6]
const g = [ ...e, ...f] // [1,2,3,1,2,3,4,5,6]
etc.Don't use var.
Rarely use let
Mutating variables can lead to unwanted side effects and introduce hard to trace bugs.
Instead use immutable operations like the ones above, that generate a new state rather than mutating the existing state.
Caveat: If writing a high compute piece of code that requires lots of recursion or traversing structures to create new immutable structures - then looping and mutation may be worth the loss of purity. Flat explains ways of handling mutation safely for these cases.
Nesting logic is when you put a logic block inside of another logic block. For example an if inisde another if or a switch inside an if etc.
Each time you nest some logic, you are tangling code that little bit more.
It makes code difficult to trace, and notoriously difficult to refactor.
Instead keep logic flat or 'normalised'.
By moving to immutable patterns ...
const a = ...
const b = ...
const c = ... ? ... : ...... and putting code blocks inside reusable functions, then you will find that nested logic no longer becomes a problem.
Welcome to flat coding :)
If you have functions that return values at different points in the function, then code becomes hard to trace.
Especially when combined with variable mutations and nested logic.
Instead use a single return and shift your logic to the right of the return using ternaries, switch-ternaries, short circuits, etc.
multiple
returnstatements are a symptom of imperative logic blocks likeifandswitch, which are 'Unsafe keywords' in flat. If your project already has them, theres not much you can do (unless you refactor), but don't introduce more.
Advanced Flat organises code into functions, and functions into libraries (libs).
🌶️ You'll find
pipein The Prelude
// Note that this is using typescript syntax
// functions
const upper = (str: string) => str.toUpperCase()
const lower = (str: string) => str.toLowerCase()
// String_ lib
const String_ = {
upper,
lower
}An oo way to think about the above is that libs are classes with static methods.
Programs are built by using pipe to pass a value through a series of functions.
pipe('cat', String_.upper, String_.lower)or
import { lower, upper } from '...'
pipe('cat', upper, lower)Which is no different than...
String_.lower(
String_.upper(
'cat'
)
)... but visualises things as a pipeline rather a nested set of functions.
Pipes can be composed...
const myPipe = compose(String_.upper, String_.lower)and then have values piped through them.
myPipe('cat')
// or
pipe('cat', myPipe, anotherPipe)A given function can be used by any value that conforms to function's call signature. For example upper and lower from the above example are usable by any strings. This is different to a method that only works on objects created from a given class.
if- encourages mutation and multiple returnsswitch- encourages mutation and multiple returnsvar- Is a higher scoped varient ofletwhich is no longer needed.
If your project already has unsafe keywords - well unless you refactor, theres not much you can do, but think first before adding more.
Loop constructs like for and while tend to be faster than recursion and are preferred in high compute situations. However they come at the cost of purity since they mutate variables in order to run a loop. For most cases recursion provides a pure way of achieving the same result with a tiny performance hit.
classnewthis
In most Flat code you won't need classes, prototypes or any other oo constructs. You will however most likely still need to use the new keyword when using other libraries.