143

I come with this:

(defn string->integer [str & [base]]
  (Integer/parseInt str (if (nil? base) 10 base)))

(string->integer "10")
(string->integer "FF" 16)

But it must be a better way to do this.

jcubic
  • 61,973
  • 54
  • 229
  • 402

6 Answers6

186

A function can have multiple signatures if the signatures differ in arity. You can use that to supply default values.

(defn string->integer 
  ([s] (string->integer s 10))
  ([s base] (Integer/parseInt s base)))

Note that assuming false and nil are both considered non-values, (if (nil? base) 10 base) could be shortened to (if base base 10), or further to (or base 10).

Brian Carper
  • 71,150
  • 28
  • 166
  • 168
  • 5
    I think it would be better for the second line to say `(recur s 10)`, using [`recur`](http://grimoire.arrdem.com/1.6.0/clojure.core/recur/) instead of repeating the function name `string->integer`. That would make it easier to rename the function in the future. Does anyone know any reason not to use `recur` in these situations? – Rory O'Kane Aug 09 '14 at 23:24
  • 13
    It looks like `recur` only works on the same arity. if you tried recur above, for example: `java.lang.IllegalArgumentException: Mismatched argument count to recur, expected: 1 args, got: 2, compiling:` – djhaskin987 Aug 20 '14 at 13:16
  • 2
    Ran into this same issue. Would it just make sense to have have the function call itself (i.e. `(string->integer s 10)`)? – Kurt Mueller Nov 25 '15 at 21:56
173

You can also destructure rest arguments as a map since Clojure 1.2 [ref]. This lets you name and provide defaults for function arguments:

(defn string->integer [s & {:keys [base] :or {base 10}}]
    (Integer/parseInt s base))

Now you can call

(string->integer "11")
=> 11

or

(string->integer "11" :base 8)
=> 9

You can see this in action here: https://github.com/Raynes/clavatar/blob/master/src/clavatar/core.clj (for example)

David Ongaro
  • 3,568
  • 1
  • 24
  • 36
Matthew Gilliard
  • 9,298
  • 3
  • 33
  • 48
  • 1
    So easy to understand if coming from a Python background :) – Dan Mar 06 '12 at 12:17
  • 1
    This is far easier to understand than the accepted answer...is this the accepted ["Clojurian"](https://github.com/bbatsov/clojure-style-guide) way? Please consider adding to this document. – yurisich Feb 11 '13 at 01:06
  • 1
    I have [added an issue](https://github.com/bbatsov/clojure-style-guide/issues/37) to the unofficial style guide to help address this. – yurisich Feb 11 '13 at 01:25
  • 1
    This answer more effectively captures the "proper" way to do it than the accepted answer, though both will work fine. (of course, a great power of Lisp languages is that there are usually many different ways to do the same fundamental thing) – johnbakers Jan 03 '14 at 08:14
  • 3
    This looked a bit wordy to me and I had trouble remembering it for a while, so I created [a slightly less verbose macro](https://gist.github.com/akbiggs/86f5bd96b7303b5c3882). – akbiggs May 22 '14 at 02:09
  • This is the solution that I like the most. However I'm not totally able to understand it. Why can you destructure the rest arguments if they are not passed as a map ? My REPL experiments report the rest arguments as a list including the keywords and the values and I'm unable to destructure them – Danielo515 Mar 26 '18 at 10:55
42

This solution is the closer to the spirit of the original solution, but marginally cleaner

(defn string->integer [str & [base]]
  (Integer/parseInt str (or base 10)))

A similar pattern which can be handy uses or combined with let

(defn string->integer [str & [base]]
  (let [base (or base 10)]
    (Integer/parseInt str base)))

While in this case more verbose, it can be useful if you wish to have defaults dependent on other input values. For example, consider the following function:

(defn exemplar [a & [b c]]
  (let [b (or b 5)
        c (or c (* 7 b))]
    ;; or whatever yer actual code might be...
    (println a b c)))

(exemplar 3) => 3 5 35

This approach can easily be extended to work with named arguments (as in M. Gilliar's solution) as well:

(defn exemplar [a & {:keys [b c]}]
  (let [b (or b 5)
        c (or c (* 7 b))]
    (println a b c)))

Or using even more of a fusion:

(defn exemplar [a & {:keys [b c] :or {b 5}}]
  (let [c (or c (* 7 b))]
    (println a b c)))
metasoarous
  • 2,854
  • 1
  • 23
  • 24
  • If you do not need your defaults dependent on other defaults (or perhaps even if you do), the solution by Matthew above also allows for multiple default values for different variables. It is much cleaner than using a regular `or` – johnbakers Jan 03 '14 at 08:13
  • I'm a Clojure noob so maybe OpenLearner is right, but this is an interesting alternative to Matthew's solution above. I'm glad to know about this whether I ultimately decide to use it or not. – GlenPeterson Jul 26 '14 at 15:56
  • `or` is different from `:or` since `or` does not know the difference of `nil` and `false`. – Xiangru Lian Jul 28 '15 at 13:37
  • @XiangruLian Are you saying that when using :or, if you pass false, it will know to use false instead of the default? While with or it would use the default when passed false and not false itself? – Didier A. Dec 15 '15 at 00:51
11

There is another approach you might want to consider: partial functions. These are arguably a more "functional" and more flexible way to specify default values for functions.

Start by creating (if necessary) a function that has the parameter(s) that you want to provide as default(s) as the leading parameter(s):

(defn string->integer [base str]
  (Integer/parseInt str base))

This is done because Clojure's version of partial lets you provide the "default" values only in the order they appear in the function definition. Once the parameters have been ordered as desired, you can then create a "default" version of the function using the partial function:

(partial string->integer 10)

In order to make this function callable multiple times you could put it in a var using def:

(def decimal (partial string->integer 10))
(decimal "10")
;10

You could also create a "local default" using let:

(let [hex (partial string->integer 16)]
  (* (hex "FF") (hex "AA")))
;43350

The partial function approach has one key advantage over the others: the consumer of the function can still decide what the default value will be rather than the producer of the function without needing to modify the function definition. This is illustrated in the example with hex where I have decided that the default function decimal is not what I want.

Another advantage of this approach is you can assign the default function a different name (decimal, hex, etc) which may be more descriptive and/or a different scope (var, local). The partial function can also be mixed with some of the approaches above if desired:

(defn string->integer 
  ([s] (string->integer s 10))
  ([base s] (Integer/parseInt s base)))

(def hex (partial string->integer 16))

(Note this is slightly different from Brian's answer as the order of the parameters has been reversed for the reasons given at the top of this response)

optevo
  • 2,016
  • 20
  • 17
7

You might also look into (fnil) https://clojuredocs.org/clojure.core/fnil

celwell
  • 1,634
  • 3
  • 19
  • 27
1

A very similar approach to Matthew's suggestion is to not do the & rest args, but require that callers provide the single extra map argument of flexible (and optional) keys.

(defn string->integer [s {:keys [base] :or {base 10}}]
    (Integer/parseInt s base))

(string->integer "11" {:base 8})
=> 9

(string->integer "11" {})
=> 11

There is a benefit to this in that the options are a single argument map and the caller doesn't have to be so careful about passing an even number of extra args. Plus, linters can do better with this style. Editors should also do better with map key-value tabular alignment (if you're into that) than pairs of args per line.

The slight down-side is that when called with no options, an empty map must still be provided.

This is touched on in this Positional Arguments section (Code Smells article).

UPDATE: There is now support in Clojure 1.11 for passing in a map for optional args (using &).

Micah Elliott
  • 9,600
  • 5
  • 51
  • 54
  • The requirement was to make it optional. Maybe adding `&` but you've said that this is the difference. – jcubic Dec 31 '20 at 14:19