Please note that as of v0.14.0 Elixir provides nested data structure access as part of the standard library. Nested will not be maintained further, and will not compile on v.0.14 or later due to name conflicts.
Updates a hierarchy of heterogeneous record/collection types, given a Structure,a list of Fields and a value (or update function).
Structure can be any hierarchy/combination of:
* Records
* Lists
* Tuples
* HashDict
* Erlang's :dict and :orddict
You can easily implement the Nested.Accessors protocol to support other structures. See accessors.ex for examples.
List of fields - list of symbols / indices in update path
* for Records, an atom must indicate accessor e.g. :name for Person.name
* for dictionaries, whatever key value
* for Tuples, a 0-based ordinal
* for Lists:
* atom (for Keyword list)
* integer for ordinal position (position in list is preserved!)
* [] - empty list (as last field) means prepend to list
Update value can be a function - in that case a record update_ accessor or Dict.update will be called as appropriate.
import Nested
defrecord Address, street: nil, city: nil, state: nil
defrecord Person, first_name: nil, last_name: nil, address: nil, phone_numbers: nil
home = Address.new(street: "101 First Street", city: "Anytown", state: "Denial")
jeremy = Person.new(first_name: "Jeremy", last_name: "Huffman", address: home, phone_numbers: ["867-5309"])
#simplest case - set a value in a nested record
jeremy = put_in(jeremy, [:address, :state], "SC")
IO.puts jeremy.address.state # "SC"
#A hierarchy can be arbitrarily deep
books = [first: [title: "A tale of two keywords", isbn: 12],second: [title: "For whom the code flows", isbn: 93]]
library = [librarian: jeremy, books: books]
#keyword lists (and dictionaries) are manipulated like records
library = put_in(library, [:books, :first, :author], jeremy)
IO.puts library[:books][:first][:author].first_name # "Jeremy"
# With a plain list we can either replace an existing item by its ordinal index
library = put_in(library, [:librarian, :phone_numbers, 0], "555-9191")
# Or we can pre-pend items to it with an empty list []
library = put_in(library, [:librarian, :phone_numbers, []], "867-5309")
IO.inspect library[:librarian].phone_numbers # ["867-5309","555-9191"]
#we can use update functions from records and dicts (and a fake one for lists)
library = update_in(library, [:books, :first, :isbn], &1 + 1)
IO.puts library[:books][:first][:isbn] # 13
#and yes, there is also a get_in ...
IO.puts get_in library, [:books, :first, :author, :address, :street] # 101 First Street
For more examples take a look at the tests.