> what's the purpose of duplicating the hash rather than creating a new empty hash?

This allows the method to work with arbitrary instances of map-like classes without making any assumptions about what class it was passed or how instances of that class are constructed. Another option is to do `hash.class.new` and assume the class has a zero argument constructor.

> Also, why does #map_keys use #map_hash but #map_vals expands out the iteration manually?

`map_keys`, like `map_hash` allows keys to be removed and replaced with new keys, this requires a delete on the key and a re-insertion to maintain the hash-order.

Since `map_vals` preserves the key, we can be more optimal and avoid the key removal and re-insertion.