Hi folks,
I’m building a library and I have an interesting case when it comes to pattern matching with maps.
Would appreciate any help or pointing me in some directions.
Goal
I’d like to be able to have a map that’s deserialized over the wire from JSON, but can still pattern match with atoms if specified.
Here’s an simplified snippet of the library, where run
is the function I exposed from the library, and the anonymous function is provided by the user.
%{foo: value} = run("do something", fn ->
%{foo: "bar"}
end)
As someone reading this, without knowing what run
is doing, the anonymous function just returns a map %{foo: "bar"}
, which will be the return value of run
itself as well, and a user would want to pattern match against it.
Now here’s the interesting part. The reason run
is a wrapper in the first place is it do some extra things to make sure this function is idempotent, and it communicates elsewhere to store the state of the returned function.
So from my perspective of the library author, when I get the result of do something
again over the wire and deserializes it, it results in %{"foo" => "bar"}
instead, and I have no way to really know beforehand what shape of the data the user is expecting it in.
Which means the attempted pattern match will raise an error.
Things I’ve thought of
If the pattern match errored, catch it and use String.to_existing_atom
to attempt again.
Might not be a big deal if the map is flat, but if the map is nested, doing that for each key iteration is likely going to slow things down unnecessarily.
Then obviously I don’t want to just do String.to_atom
because we all know atoms are not GC’d. Also just preemptively converting all keys in a map to atoms is also likely to be waste of CPU cycles if they’re not utilized.
Technically speaking, this can also apply to the values of maps as well since someone would want to declare a map that have atoms for both key and values for some reason.
Then that’s even worst.
So rephrasing the question again. How would I take a deserialized JSON map, but will be able to pattern match against a user defined map regardless of the type being a String
type or :atom
type.
Thanks in advance!
Note
I care less about other data types atm since they aren’t like String
and :atom
, where essentially they’re string literals in different presentations with some different characteristics, but are somehow not fully compatible with each other.
If you’ve used Ruby/Rails before. I basically want something like HashWithIndifferentAccess
require "active_support/hash_with_indifferent_access"
framework = ActiveSupport::HashWithIndifferentAccess.new
framework[:name] = 'Ruby on Rails'
puts framework[:name] # Ruby on Rails
puts framework['name'] # Ruby on Rails
And yes, I know it’s generally frowned upon in Elixir, but as a library author, I don’t have control over the data a user might be putting into it.
Hope this helps with the context.