3
votes

Clojure has a very nice destructuring syntax, which works for both arrays and maps.

I am often confused between the two, meaning I'm not sure which one to choose. When should I use maps as parameters, and when should use an array ?

For instance, I came accross the following data, I want to pass a longitude and a latitude. I could pass it either as {:lat 12 :lng 34} or [12 34], or as two parameters.

Note : I don't use two parameters since I think it's nicer to have a coords binding.

Then suppose I want to add new fields (precision, altitude, timestamp...), it seems that the advantage then goes to the map :

  • An array doesn't seem very readable with more parameters (you have to look at the prototype to understand the destructuring and therefore the parameters used).
  • In a map I can add new fields and printing it is self-descriptive (keys acts as labels).

But then I often end up with functions taking a big options parameter as a map, with some unrelated sub-options. It feels a bit bloated, even with just 10 keys.

So, when should I use map, and when should I use arrays in my function parameters ? What are the pro/cons in terms of readability/extensibility/performance ? Also, could core.match help in that case ?

3
dont forget you can easily convert maps into records, thus gaining java-fast field access performance - while maintaining most of what maps can do (regarding access); you can't do that with vectorsbirdspider
@birdspider That's very interesting. But why can't we do that with vectors ? From your pastebin, I can't tell the difference ? (I never used records, maybe that's why, but it seems like it just takes a list of arguments, which sounds closer to an array than a map ?).nha
in my pastebin I just outlined the easy of using records interchangebly instead of maps, with records you gain all sorts of stuff (i.e. performance) without sacrificing the flexibility of maps; more stuff: user=> (assoc (LngLat. 0 0) :lat 3) #user.LngLat{:lng 0, :lat 3} and user=> (map->LngLat {:lat 3 :lng 2}) #user.LngLat{:lng 2, :lat 3} how would you do that without key-names ?birdspider
further records actually generate a class, so you can use protocol dispatch on LngLat, with maps you would have to use multimethods (I think, or other less clever form of duck-typing) because how would you/clojure differenciate between say a Point {:x :y} map and a my.name.space.CartesianFoobar {:x :y} mapbirdspider

3 Answers

2
votes

To summarize (although there's probably much more to it)

I'd say that position based data structures like vectors hit their limit very early. Especially if the data structure is used in more than a few places and is more "descriptive" than "listy".

Why maps?

Initially they are easy to work with and very flexible, further on in development they can easily be exchanged for records which are nearly as flexible as maps but:

  • are actuall classes/types
  • therefore have pure-java field access characterisics
  • therefore play well with protocols/interfaces
  • but are nearly as flexible as maps and
  • play nicely with various destructurings
  • they provide a suite of util functions you do not have to implement yourself
    • (map->LngLat {...}) (produces a LngLat from a map) and
    • (apply ->LngLat [...]) (constructs a LngLat from a vector)
    • probably more ...
1
votes

As @birdspider indicated in the comments, records can help you organize the destructuring of a map along with many other benefits.

You also have a fair amount of flexibility in how you construct records. You either use the vector-style positional arguments or pass in keys as a map.

This blog post by Stuart Sierra gives a nice overview of the capabilities.

0
votes

Another option is to use keyvals: a sequence of alternating keys and values as trailing arguments. Several core functions are set up this way:

(hash-map 1 2 3 4) ;{1 2, 3 4}

(assoc {1 2} 3 4) ;{3 4, 1 2}

You can take the keyvals into a map or bind them to names as suits the case.