Skip to content

Instantly share code, notes, and snippets.

@iest
Last active October 19, 2022 08:46
Show Gist options
  • Star 29 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save iest/69c6b0fe0dac14b0916ce57b56d9c209 to your computer and use it in GitHub Desktop.
Save iest/69c6b0fe0dac14b0916ce57b56d9c209 to your computer and use it in GitHub Desktop.
Moving from lodash/fp to ramda

Moving from lodash/fp to ramda

How

Good news is we're only using lodash/fp, which makes it easier to match function signatures.

  1. Find most-used lodash methods, we'll convert these first maybe?
  2. Go through each lodash method, find the ramda equivalent where possible
  3. Write a codemod to rewrite those usages
  4. Who the fuck thought function aliases were a good idea

Codemod

  • import R from 'ramda'
  • Use as R.X, instead of import { X } from 'ramda'

Currently used lodash methods with usage:

  • 245 x noop
    • No equivalent
  • 62 x flowRight
    • R.compose
  • 26 x identity
  • 16 x curry
    • R.curry
  • 13 x get
    • R.path
    • NOTE: ramda paths use arrays, not strings
  • 13 x debounce
    • No equivalent
  • 12 x find
    • _.find(collection, [predicate=_.identity], [fromIndex=0])
    • R.find (a → Boolean) → [a] → a | undefined
  • 11 x range
    • _.range([start=0], end, [step=1])
    • R.range Number → Number → [Number]
    • Does not support steps
  • 9 x findIndex
    • _.findIndex(predicate, array)
    • R.findIndex (a → Boolean) → [a] → Number
  • 8 x some
    • _.some(predicate, collection)
    • R.anyPass [(*… → Boolean)] → (*… → Boolean)
  • 8 x map
    • _.map(iteratee, collection)
    • R.map Functor f => (a → b) → f a → f b
  • 7 x flow
    • _.flow(funcs)
    • R.pipe
  • 7 x first
    • Alias of _.head
    • R.head
  • 6 x last
    • _.last(array)
    • R.last
  • 5 x isEqual
    • _.isEqual(value, other)
    • R.equals a → b → Boolean
  • 5 x filter
    • R.filter
  • 5 x every
    • _.every(predicate, collection)
      • predicate called with (value, key|index, collection)
    • R.all
      • predicate called with (value)
  • 4 x pick
    • _.pick(props, object)
    • R.pick [k] → {k: v} → {k: v}
  • 3 x reduce
    • _.reduce(iteratee, accumulator, collection)
      • iteratee is invoked with four arguments: (accumulator, value, index|key, collection)
    • R.reduce ((a, b) → a) → a → [b] → a
      • iterator function receives two values: (acc, value)
  • 3 x rangeStep
    • Lodash FP's 3-arity version of range
    • _.rangeStep(step, start, end)
    • No direct equivalent, closest would be:
        const rangeStep = (start, step, stop) => R.map(
          n => start + step * n,
          R.range(0, (1 + (stop - start) / step) >>> 0)
        );
  • 3 x compose
    • Alias of flowRight
    • R.compose
  • 2 x values
    • _.values(object)
    • R.values {k: v} → [v]
  • 2 x takeRight
    • _.takeRight(array, [n=1])
    • R.takeLast
  • 2 x take
    • R.take
  • 2 x split
    • R.split
  • 2 x merge
    • Deep merge from left to right
    • R.mergeDeepLeft
  • 2 x memoize
    • No equivalent
  • 2 x inRange
    • _.inRange(number, [start=0], end)
    • Checks if n is between start and up to, but not including, end. If end is not specified, it's set to start with start then set to 0. If start is greater than end the params are swapped to support negative ranges.
    • No direct equivalent
    • R.both(R.gte(R.__, start), R.lte(R.__, end))
  • 2 x includes
    • R.contains
  • 2 x groupBy

  • 2 x getOr
  • 2 x concat
    • R.concat
  • 2 x clamp
    • _.clamp(number, [lower], upper)
    • R.clamp Ord a => a → a → a → a
  • 1 x uniqBy
  • 1 x uniq
  • 1 x toPairs
  • 1 x toLower
  • 1 x throttle
  • 1 x spread
  • 1 x sortBy
  • 1 x slice
  • 1 x set
  • 1 x reverse
  • 1 x replace
  • 1 x reject
  • 1 x pluck
  • 1 x pickBy
  • 1 x negate
  • 1 x join
  • 1 x isObject
  • 1 x isEmpty
  • 1 x intersection
  • 1 x fromPairs
  • 1 x flatten
  • 1 x equals
    • Alias of isEquals
    • R.equals
  • 1 x delay
    • No equivalent
    • Use setTimeout
  • 1 x defaultTo
  • 1 x **curryN
    • R.curryN
  • 1 x ceil
    • _.ceil(number, [precision=0])
    • No equivalent
    • Use Math.ceil instead
  • 1 x assign
    • _.assign(object, [sources])
    • R.merge {k: v} → {k: v} → {k: v}
@JamieDixon
Copy link

This is awesome! Btw, most of our usages of noop are erroneous and need replacing. I'll be working on that soon.

@hungtrinh
Copy link

Use as R.X, instead of import { X } from 'ramda'

To reduce bundle file size, so i thinks import { X } from 'ramda' is better than import R from 'ramda'

@vorobiovkirill
Copy link

_.xor -> R.symmetricDifference

@fa7ad
Copy link

fa7ad commented Nov 6, 2019

_.noop -> R.always(undefined)
or just () => {}

@stevenvachon
Copy link

@hungtrinh, you'll still need a treeshaker for that reduction to occur, and such would detect unused code. Still, though, I agree that it's clearer to import only the named exports that are used.

@iest
Copy link
Author

iest commented Dec 3, 2019

To reduce bundle file size, so i thinks import { X } from 'ramda' is better than import R from 'ramda'

This fully depends on your build system. If you're using babel, you can use babel-plugin-ramda to do that transformation work fo you — so you can use R.X in your source files and only the ramda functions you use get included in the build.

And using R.X is much better for dev IMO, as you might not know exactly what ramda functions you need when you're writing compositions.

At least, that's what I do!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment