- Functions
- First class
const getServerStuff = callback => ajaxCall(json => callback(json));
copied from Mostly Adequate Guide - Pure functions (referential transparency)
A pure function is a function that, given the same input, will always return the same output and does not have any observable side effect. Subtle example of an impure function:
let minimum = 21; const checkAge = age => age >= minimum;
- Side effects (not bad, but should be contained)
Impure functions can be made pure by wrapping them in a function (thunk):
() => Math.random()
"Dependency injection" - Partial application and currying (also mention closures?) Partial application allows "configuration" Currying helps to compose n-ary functions and may force different order of arguments
- Compose and pipe
- Total/partial functions (nice bridge to
Maybe
/Option
andEither
/Result
) - Point-free style (tacit programming) Subjective
- Hindley-Milner type signatures
- First class
- Declarative vs imperative? Expressive code (see chapter 8.1 in FP for sceptics)
- In declarative code there's fewer variables, often only at the edges of the program; data and behavior are separated
- Explain:
map()
(alsomap2()
?)of()
(a.k.a.return()
)flatMap()
(a.k.a.chain()
(?) orbind()
)apply()
(Promise.all()
is maybe a tiny bit similar?): apply a (curried) function wrapped in a monad to arguments wrapped in a monad See: https://youtu.be/d2yLsBn_Lz0?si=VBXWuNY3V0sj_5m8&t=2544 and https://ramdajs.com/docs/#apreduce()
(a.k.a.fold()
)?traverse()
?- Functor:
map()
, Applicative:of()
andapply()
, Monad:of()
andflatMap()
- Algebraic Structures (implemented by Type classes), probably also tell about types of polymorphism?
Ord
,Eq
- Functor
Array
Maybe
/Option
Either
/Result
IO
Task
- Monad
- Applicative
- Algebraic Data Types
- Sum types
- Product types
- Architecture?
- Stratified Design
- Onion Architecture
- DDD
- Universal Process Pattern?
Also incorporate?
lift0 = a -> SomeType a
lift1 = (a -> b) -> (SomeType a -> SomeType b)
lift2 = (a -> b -> c) -> (SomeType a -> SomeType b -> SomeType c)
- Use https://sli.dev/guide/syntax#monaco-runner
- Rename to something like "Functional Programming in JavaScript"?
- Separating code into data, calculations and effects
- Effects are "contagious" (similar to promises)
- State:
- Immutable
- Store state snapshots in an array (bonus)
- Flow: event listeners (effects) -> next state (calculations) -> update UI (effects) (visualize this)
- Currying
- Ramda's auto-currying
- Partial application
- Order of arguments: value last, or: from least to most likely to change
- Function composition
- Programs change state by passing it from function to function; they're "pipelines" (visualize, also mention sequential and parallel computations)
flow
vspipe
vs lambda (rule of thumb: named functions always have explicit args, inline functions only have args when they're needed in scope)- When and when not to use point-free style
- When to extract code to new functions vs when to leave it inline
- Abstractions
- When to use syntax (e.g. operators, property accessors) vs functions (answer: functions as much as possible, they're composable, but syntax is more familiar)
- First-class functions
- Function composition using currying. Order of arguments: from least to most likely to change. Practice with some exercises. E.g.: implement / practice with common ramda functions like
prop
andmap
. - Implement
pipe
andflow
. Explain when to use which (point-free style). E.g.: make them implementsort
,ascend
andprop
so they can be used like this:sort(ascend(prop('value')))
. - Pure functions. It's not always black and white. Do a pop quiz and ask people how pure functions are. (Kyle Simpson does something similar)
- Data, calculations, effects. Use a single, global, simple (not too nested; leave data that can be derived) data structure for state.
-
Structure of the code: impure input (events) -> next state through pure functions -> impure output (rendering). "Impureim sandwich". todo: how is input to a program always impure?Better to explain stratified design and/or onion architecture. - Ramda's auto-currying.
- Explain Quokka.
- Explain Yahtzee.
- Explain the code:
- Explain file structure:
data.js
,app.js
,calculations.js
,effects.js
andlib
folder. - Explain instructions: the numbers in the code corresponding to the steps; the icons.
- Mention linter
- Explain file structure:
- For the last "big exercise": refactor code that does something with data coming from something like Contentful. The code is imperative, first step is to use Array methods, then function composition, finally maybe monads, functions, applicatives?
- Start with replacing imperative iterations to Array methods, then to curried ramda functions.
- Implement flatMap, map, filter, take, groupBy using only
reduce()
- Create a game with simple AI players and just logging as output (e.g. https://youtu.be/vK1DazRK_a0?si=POqr1UnSS2GO5ESw&t=3124)
- Server side form validation
- File upload:
- for each file:
- validate (size, mimetype) -> create hash -> get presigned url -> upload file
- show progress bar
- show any errors
- retries?
- for each file:
- Create AND and OR operators using lambda calculus: https://youtu.be/eis11j_iGMs?si=rw_AqZmHBNRTX6Ty (maybe also explain the y-combinator that makes recursion possible)
- Implement strategies for the Prisoners Dilemma according to Veritasium's video
- When using js, there's less type safety, so make functions that wrap the curried map, filter, reduce, etc functions that add type safety at runtime. Similar to Spec in Clojure.
- map, filter, reduce, etc that also work for Objects, Maps and Sets
- Declarative API for DOM or Web APIs
- Pretend FE frameworks don't exist and create your own
- Parse s-expressions
- move (shared) state into a single structure
- replace redundant state with derived state (calculations)
- identify mutations and effects (and more?)
- replace mutations with pure functions
- move effects to the boundary of the system
- distribute functionality over generic helpers, domain functions and effects (?)