Clojure : Maps of Lists of Maps of ... How to wrangle anyonymous data structures

595 views Asked by At

Hi guys : In java we've all had the experience of using our ide to "traverse" down the depths of a complex data type :

dog.getCollar().getCollarTag().getName(); 

However, in Clojure, this becomes non trivial, due to the lack of static typing. How do we "defend" or Clojure against complexites that might arrive from nested data structures?

1) An advisable "limit" to the depth of clojure data structures ?

and

2) A common idiom for dealing with abritrarily deeply nested data structures, which prevents errors such as mistaking a list for map, or failing to correctly lower/upper case a variable name ?

Forgive me if I'm sounding a little off paradigm here ... It might be the case that such errors are efficiently curbed by continually testing in the REPL .. However, I was wondering if there are any other methods for making sure, at compile time, that code is as correct as possible (i.e. unit tests, IDE / emacs plugins, etc...)

5

There are 5 answers

1
ponzao On

Testing the correctness of your functions in the REPL is a good start. A good way to improve on that are unit tests and perhaps usage of pre- and postconditions (http://objectcommando.com/blog/2010/03/07/design-by-contract-with-clojure/).

When using keywords your example can be turned into (get-in dog [:collar :tag :name]). And "modifications" with assoc-in or update-in.

0
Looka On

How do we "defend" or Clojure against complexites that might arrive from nested data structures?

You can't. It's one of the failings of this language. If you want static typing, then clojure is not your thing. A lot of people will recommend use of nested maps, which are slow, provide no safety at all and are not a datatype. Map is a datatype, nested maps are not, they are pretend objects. Much like javascript, clojure requires the programmer to keep track of what the contents of various pieces of data actually are since pieces of data in Clojure are composites of like 5 different datastructures rather than a datastructure themselves.

2
sortega On

Chained getters are often a violation of the Law of Demeter. In doing so you are coupling your code to the whole data structure and this can be tricky and complex. In functional languages as clojure feels more natural to use recursion and sequence processing to keep it simple and idiomatic. So:

  1. Tell, don't ask. Instead of asking for "the name of the tag of collar of the dog..." try to ask the dog object or a high-level function to do what you want.
  2. As ponzao says, test, test and test. Test on the repl to explore your options and write small unit tests as a
0
Arthur Ulfeldt On

keywords are your friend here

(def my-animals  { :dog {:collar {:tag {:name "fido"}}}})

try to stick with maps because they are more inherently self describing.
Then play with the thread first and .. macroes

(-> my-animals :dog :collar :tag :name)

and take advantage of the fact that keywords are also functions that look them selves up in a map.

When you work in an IDE with code completion it is parsing the code and then building data about that code, then parsing that data to produce it's suggestions. If your data is in it's self declarative (it can be 'read') then you get this even without the IDE.

ps: this leads to the standard rant about "code is data" has been expressed many times over by people much smarter than I :)

0
Odinodin On

There is a great library that I find really helpful in this respect, which is the Schema library from the guys who make Prismatic. It provides a way to add optional typing for nested data structures such as the ones you are refering to in your question. This can be enforced at runtime (or just when testing) in order to validate the input to your functions. In addition, it also serves as great documentation, describing the assumptions a function has on its input (which is no small thing).