Kevin Kostlan
Kevin Kostlan

Reputation: 3519

Clojure handling requests to non-existant fields

Clojure varies on what happens when the requested element does not exist:

(get {:a 1} :b) ; nil, no b
(second [0]) ; nil, only one element.
; nil seems to be by far the most common but:
(nth [0] 1) ; ERROR: java.lang.IndexOutOfBoundsException.
(nth nil 1) ; but this works (returning nil)

Is there a general logic behind when to return nil and when to throw an error?

Upvotes: 1

Views: 116

Answers (3)

Joseph Yourine
Joseph Yourine

Reputation: 1331

For me the logic is :

  • nth : you assume that data exist AND you want to have its index
  • get : it is more exploratory, you ask for data and if it does not exist it returns nothing

In both cases you can pass a value if the value is not found

(nth [1 2] 3 nil) = nil

EDIT

In a more pragmatic perspective, I would also add thse inputs.

In general, you will process in a Clojure program several "rows"/"records" and not single data. Imagine you have a list of items like

({:name "A" :type "bicycle" :range "premium"} {:name "B" :type "helmet"}) 

Here you have a list of items (for instance in a shop). Let's say you have defined ranges for your bicylces but not for helmets so that this property does not exist for records associated with helmets. Then if you process all this data using map (or reduce, etc.) you might want to output a default value for :range, like "normal" or "none" in order to show it on a UI. You can also use a cond or filter based on the result to split up data. Etc.

The function nth is used on vectors. First, using a collection of vectors to represent a record would be akward (a subcollection OK, but not the primary one). If I really want to use it instead of a hashmap for normalized data structures, it would be for a constant lenght and with positions clearly defined (for instance [purchase_price selling_price]. So if I use nth for big data, it would be for on a subcollection.

First I "query" a main collection

((first items) :name) = "A"

Then on a secondary table, I will track the position (for instance if I have a vector to represent items sorted by user score ; a vector is made for sorted data)

(nth ranks "A")

Here I know that the article exist and if is not on the rank table, it means that there is a problem in data, so that I want an error.

Vector -> homogeneous or sorted data Map/Record -> non sorted properties that might exist

Upvotes: 3

Piotrek Bzdyl
Piotrek Bzdyl

Reputation: 13185

get does a lookup by a key in the collection. It works for maps and vectors (where index is a key). Lookup returns nil if the key is not found.

second works on seqs and is actually implemented on top of next and first. They return nil when no value is available (with an addition of rest which returns empty seq when no value is available).

nth works with sequences and assumes that the requested index can be found in the collection.

As for the logic behind them I think it gives you a choice and decide if an index is out of bounds is an error or is a valid state. By choosing between get and nth you might decide to fail fast or use nil as a default value.

Upvotes: 3

kongeor
kongeor

Reputation: 732

In general nil is preferred (although this can be context specific), because it is easier to work with. In your case instead of nth you can use get which also works for vectors:

=> (get [0] 1)
nil
=> (get nil 1)
nil

Upvotes: 0

Related Questions