TLDR; I have started a project to provide examples of how to convert from Lodash to fp-ts and I could use your help! Please consider chipping in, all PRs are welcome!
Lodash is the single most downloaded package on npm. It deserves its place at the top - it provides a massive suite of functionality that is performant and has a clear, consistent interface. Lodash is, without a doubt, a fantastic Javascript library.
However, Lodash was written before the advent of Typescript and has significant holes when it comes to typed functional programming. Let me hit you with an example:
const log = console.log // just to make things a little nicer to read
const obj = { 'a': [{ 'b': { 'c': 3 } }] }
const result = _.get(obj, 'a[0].b.c')
const ohno = _.get(obj, 'a[0].b.d')
log(result) // 3
log(ohno) // undefined
What type is result
? Why, it's the any
type! Not only are we missing type information on the result, we are also missing type information on the path we provided - if someone renames c
to d
we won't know until it gets all the way to production and explodes. On top of that we have to remember to check for undefined everywhere it might exist. Hope you never forget!
There is a better way! Let's look at how to do this using libraries that were designed from the ground up for typescript (fp-ts and monocle-ts):
import * as Op from 'monocle-ts/lib/Optional'
const getC = pipe(
Op.id<{ a: readonly { b: { c: number } }[] }>(),
Op.prop('a'),
Op.index(0),
Op.prop('b'),
Op.prop('c'),
opt => opt.getOption,
)
log(getC(obj)) // { _tag: 'Some', value: 3 }
Aw yeah. This is a technique known as Optics
and it provides type safety through and through. Notice that we are providing a type with id
- any calls to prop
that don't align with the type will error. Finally, we're safe from Dave a few desks down who is constantly renaming things. We also have a strong return type - Option<number>
. Option
will force us to remember to add error handling in case our object is malformed, and number
because we know that c
is a number.
Here's another example:
var mutable = {a: 0, b: 2}
log(_.assign(mutable, {a: 1, c: 3}))
log(mutable)
// { a: 1, b: 2, c: 3 }
// { a: 1, b: 2, c: 3 }
Let's try again, this time with a library that is consistently immutable:
import {merge} from 'fp-ts-std/Record'
var mutable = {a: 0, b: 2}
log(merge(mutable)({a: 1, c: 3}))
log(mutable)
// { a: 1, b: 2, c: 3 }
// { a: 0, b: 2 }
Oh thank goodness, we're safe.
In my opinion, the biggest hurdle to widespread adoption of fp-ts is a lack of good examples. Almost everyone is familiar with Lodash - why not provide a set of examples that would help everyone transition?
Well I've started doing just that. I hope as people see the conversion is simple, and the benefits provided are significant, fp-ts will become even more widespread. Wouldn't that be a wonderful world?
Working through all the Lodash functions can take a long time, however, and I am (gasp) sometimes wrong. If you are reading this and have a few minutes, please take a crack at helping me with this project. PRs are very welcome!
Top comments (3)
Here's an example I used in a PR at work the other day to advocate against using lodash now that we've moved to Typescript:
Great article! Here are another couple simple examples showing the advantages of fp-ts's strong type paradigms:
log(A.filter(x => x ? true : false)([0, 1, false, 2, '', 3]))
->
log(A.filter(Boolean)([0, 1, false, 2, '', 3]))
or
instead of
Boolean
, one that also narrows the type:function isTruthy<T>(item: T | null): item is T { return Boolean(item) }