Function programming is a collection of approaches and tools. We aren't dogmatic about using all of them. For Javascript, we don't use these often:
- Currying
- Functors
- Function composition
- Recursion
We definitely use these a lot:
- Higher order functions
- Pure functions
- Minimal side effects
- Immutability (or we treat variables as immutable in Javascript)
Pass in all of the arguments for a function that it needs. This makes it clearer, more reliable (pure) and testable
Only use side effects for the following cases (with few exceptions):
- Outputing results such as writing to a database, responding to an API request or writing to file. Updating the DOM is a valid side effect but React takes care of this for you
- Logging
In practice, this means:
- Almost every function, if using a collection, will use
map
,filter
,reduce
orconcat
. Sometimes these functions are used for manipulating strings as well. - Use
const
instead oflet
and what that implies for your code. What does it imply? If you uselet
it means you are assigningnull
or an empty collection to a variable early, then mutating it elsewhere with imperative constructs such asfor
loops andpush
. If you useconst
it means you are determining the value once at assignment. If it can be done inline usingmap
,filter
orreduce
, do it. If it's more complicated, create a standalone function to return the value.
// Not great (warning flags: let, forEach, push)
const initVals = [4, 8]
let squared = []
initVals.forEach(n => {
squared.push(n*n)
})
// Better
const initVals = [4, 8]
const squared = initVals.map(n => n*n)
- Switch statements are somewhat of a special case. Unfortunately in JS, you can't assign a switch statement result to a variable. So wrap the switch in a function:
// Can't do
const switchResult = switch (opt) {
case 1:
return 'One'
case 2:
return 'Two'
default:
return ''
}
// Can do. Notice the returns instead of `break`s
function getSwitchResult(opt) {
switch(opt) {
case 1:
return 'One'
case 2:
return 'Two'
default:
return ''
}
}
const switchResult = getSwitchResult(opt)
- Prefer ternaries over multi-line, mutating if/else statements:
// Not great: imperative, mutating
let buildingType = null
if (String(a).toLowerCase() === 'house' || String(a).toLowerCase() === 'condo') {
buildingType = 'Residential'
} else {
buildingType = 'Commercial'
}
// Better: Single assignment to buildingType
// Note: You could also use _.toLower(a) which gaurds against nulls
const buildingType = (String(a).toLowerCase() === 'house' || String(a).toLowerCase() === 'condo') ? 'Residential' : 'Commercial'
Guard against empty collections and null values. This is important if you are chaining off the value and for simpler code following the guard. Typescript can make this less necessary but still often needs to be done. Early returns can also speed up code.
// Collection guard
// If `coll` is empty this code would break if the guard wasn't there
// Note: you could also use _.sum(coll) which is already gaurded against empty collections
function sumCollection(coll) {
if (_.isEmpty(coll) || !_.every(coll, n => _.isFinite(n))) {
return 0
}
return coll.reduce((accumulator, currentValue) => accumulator + currentValue)
}
const summedCollection = sumCollection([1, 2, 3])
Some languages and Javascript styles use these. We don't. Read up on functional programming for reasons why.
If you have to have a function called writeFiles(a,b):void
that is clearly for side effects, fine. But if you have a function like runModel(a,b): string
that both returns data and creates side effects, break it up so that side-effecting functions don't also return values. Side effecting functions should have as minimal of processing as possible.
Read these first:
- An introduction to functional programming in JavaScript
- Introduction to Functional Programming in JavaScript
- Jafar Husain's functional onboarding for Netflix (actually do these excercises but skip the zip functions if you want)
- I've learned a lot from Eric Normand in a lot of different context. He is primarily a Clojure educator, but his functional programming book uses Javascript for examples: https://www.manning.com/books/grokking-simplicity
- Learn Clojure. It will make you a better programmer and thinker in any language
Closing issues with commits
When closing a GitHub issue, it's important that the commit or commits that close that issue show up in the issue. You can do this automatically by putting "close, closes, closed, fixes, fixed" and the issue number in the commit message. Such as "documenting sqlite usage, closes #3".
Read more: https://github.blog/2013-01-22-closing-issues-via-commit-messages/
You don't have to do it this way. You can also just put the commit urls in the comments after closing the ticket.
Implied here is that a commit should be a single-issue fix. It should not fix a bunch of issues. You could have multiple commits contribute to a fix for a single issue though.