TL;DR: I’ve published an Elixir package of utility functions called Algo.
My most favourite JavaScript package is Ramda.js. It was my gateway into functional programming, and it's still one of the first packages I add to a JS project many years later. It's super useful for data manipulation eg. when working with nested data structures.
Elixir already has an excellent standard library, and Enum along with Kernel.put_in and related functions go a long way. But I found that it's still less than trivial to do a lot of transformations when working with nested maps and keyword lists, deep-merging data, or computing things like list permutations. I haven't found an Elixir package like Ramda that conveniently packages all of that, although there are packages like ex_rose_tree which do some things (and that's why there is no zipper implementation in Algo).
Here is an example of manipulating nested maps with Algo:
normalised_profile =
payload
|> Algo.rename_keys(%{
customer_id: :id,
firstName: :first_name,
lastName: :last_name
})
|> Algo.put_new_path([:profile, :preferences, :currency], "NZD")
|> Algo.update_path([:profile, :contact, :email], "unknown@example.com", &String.downcase/1)
|> Algo.replace_path([:metadata, :source], :crm_webhook)
|> Algo.evolve(%{
profile: %{age: &String.to_integer/1},
metadata: %{score: &round/1}
})
|> Algo.delete_path([:profile, :contact, :temporary_phone])
Hopefully this is quite intuitive: take some nested input data, rename a few fields, add defaults, repair a nested value, coerce a couple of values, and drop something that should not be persisted. These functions support both nested maps and nested keyword lists.
Without a package like Algo, this sort of code would probably be a mixture of Map.update, put_in, update_in, pattern matching, private helper functions, and a few slightly different versions of the same thing across the codebase.
Another example is collection work. Given a set of records, you might want to extract just the fields needed by the UI, index the full records for later lookup, and calculate grouped totals:
queue_rows =
referrals
|> Algo.project([:id, :patient_name, :speciality, :priority_score])
referrals_by_id =
Algo.index_by(referrals, & &1.id)
minutes_by_speciality =
Algo.reduce_by(referrals, & &1.speciality, 0, fn referral, minutes ->
minutes + referral.estimated_minutes
end)
The idea is to replace common, boilerplatey code with more compact, more descriptive functions.
Naming
One design choice I’ve been thinking about is naming.
The top-level Algo namespace mostly uses traditional verb-oriented operation names such as get_path, put_path, delete_path, deep_merge_right, project, index_by, and transpose.
However, I don't really like the implication of an imperative sequence of operations that verb-based naming has: do this, then update that, then transpose it.
So I’ve also included an Algo.Alt namespace. Algo.Alt provides alternative names that describe the returned value or immutable result rather than the operation: value_at_path, with_path, without_path, deep_right_merged, projection, indexed_by, humanised, and so on.
That means the code can be written in a style that emphasises the value being produced:
alias Algo.Alt, as: Algo
base_referral =
defaults
|> Algo.deep_right_merged(clinic_overrides)
normalised_referrals =
Enum.map(raw_referrals, fn referral ->
referral
|> Algo.with_renamed_keys(%{
referralId: :id,
patientName: :patient_name,
conditionCode: :condition_code
})
|> Algo.deep_right_merged(base_referral)
|> Algo.with_new_path([:triage, :clinic], "unassigned")
|> Algo.with_path([:audit, :ingested_by], "referral-worker")
|> Algo.with_updated_path([:triage, :required_documents], [], &Enum.uniq/1)
|> Algo.evolved(%{priorityScore: &String.to_integer/1})
end)
I like this style because it makes immutability more visible.
Compared to updated_data = put_path(data, path, value), updated_data = with_path(data, path, value) reads less like “mutate data” and more like “updated_data is data with an extra path”.
So that is the underlying motivation for the package: general purpose, composable, practical helpers which make data transformation code read more clearly.