There is actually a lot of complexity to hashes in Ruby.
The two major sources of complexity are the fact they are mutable and the fact that keys are mutable but hashes are computed when the object is first added.
So if you construct a valid hash, then call three functions passing them the hash, you have no idea if the hash is still valid for the second and third functions without reading the code for all the preceding functions.
What this tends to mean in practice is you are always having to check all over the place that your hash is valid.
They might look the same as clojure hashes but in practice they are used very differently.
> There is actually a lot of complexity to hashes in Ruby.
I agree here
> So if you construct a valid hash, then call three functions passing them the hash, you have no idea if the hash is still valid for the second and third functions without reading the code for all the preceding functions.
I don't find that to be true, even in the Rails codebase. You pass a hash to a function (method) and expect that it won't be mutated. If you're writing a method and you mutate a hash, you're expected to dup the argument so it won't be mutated. This is the convention. There are times when hashes are mutated, but generally that's reserved for methods who's purpose are to mutate its arguments.
Where Rails gets into trouble in its functional passing of hashes isn't in the mutability of the hashes, it's in the composability of the functions. I've never written about this problem before, so i'm not sure I have a great example but it's definitely a problem.
One example in rails is `url_for` it takes a hash argument. The problem is that there's multiple `url_for` methods that all do slightly different things. Some are needed for generating links in email, while some work in your views, and others are designed for you to use programmatically outside of a view context. One of the hardest things about this method is that one `url_for` can call another `url_for`. Since we can never be guaranteed the order of the calls it is really it makes things like having default values, or optional keys. You have to replicate logic in different functions since some may never be called in the order you might expect. This significantly impacts our ability to refactor which in turn impacts our ability to make performance improvements.
I recently did a bunch of perf work in https://engineering.heroku.com/blogs/2015-08-06-patching-rai... and some of my biggest perf improvements were getting rid of duping and merging of hashes. If Ruby had an immutable and performance efficient hash then it would have helped a bunch, however I don't think it would make the general awfulness that is an entirely hash based API to a very complex action (such as url_for) that significantly better to work with.
Take a look at ring for clojure, which is a web server interface very similar to ruby rack.
Because it passes every request as a map you can hook functions (middleware) in between that change the behaviour of the request handling, for example they could add a field for passed params or fields for user authentication.
This is possible because maps are easily extendable and functions down the line don't need to know about additional keys. You can't do that with OO properly.
The two major sources of complexity are the fact they are mutable and the fact that keys are mutable but hashes are computed when the object is first added.
So if you construct a valid hash, then call three functions passing them the hash, you have no idea if the hash is still valid for the second and third functions without reading the code for all the preceding functions.
What this tends to mean in practice is you are always having to check all over the place that your hash is valid.
They might look the same as clojure hashes but in practice they are used very differently.