There are several ways for providing optional arguments and their default values. Which to choose depends on how you want to provide those optional arguments to the function call.
Strictly Serial Arguments
Your arguments are already ordered by decreasing likelihood for setting them. You wish to be able to call the function like this (assuming a println function body and ignoring the nil return value):
user> (get-user-projects-by-user-id :conn :uid)
[:conn :uid asc nil 10 10]
user> (get-user-projects-by-user-id :conn :uid :sort)
[:conn :uid :sort nil 10 10]
user> (get-user-projects-by-user-id :conn :uid :sort "filter")
[:conn :uid :sort filter 10 10]
user> (get-user-projects-by-user-id :conn :uid :sort "filter" 9)
[:conn :uid :sort filter 9 10]
For this, you can overload the function by arity as you already started:
(defn get-user-projects-by-user-id
([db-conn userid]
(get-user-projects-by-user-id db-conn userid
(str "asc")
nil
10
10))
([db-conn userid sort]
(get-user-projects-by-user-id db-conn userid
sort
nil
10
10))
([db-conn userid sort filters]
(get-user-projects-by-user-id db-conn userid
sort
filters
10
10))
([db-conn userid sort filters offset]
(get-user-projects-by-user-id db-conn userid
sort
filters
offset
10))
([db-conn userid sort filters offset size]
(println [db-conn userid sort filters offset size])))
It's a bit tedious in the function definition and you must take care when refactoring to keep the default values correct.
Optional Arguments as a Map
You can use destructuring in your argument vector to allow passing a map with extra arguments. This allows passing them in any order: you can override offset without having to pass sort and others, too:
(defn get-user-projects-by-user-id-extra-map
[db-conn userid & [{:keys [sort filters offset size]
:or {sort "asc"
filters nil
offset 10
size 10}}]]
(println [db-conn userid sort filters offset size]))
You use it like this:
user> (get-user-projects-by-user-id-extra-map :conn :uid {:offset 9})
[:conn :uid asc nil 9 10]
Optional Arguments as Pairs
If you change the destructuring slightly (note the missing []), it allows you to pass the optional arguments as key-value-pairs without the need for a map. This is usually easier to use when your function calls are all explicit whereas the previous option is often easier when you apply your function with programmatically collected extra arguments.
(defn get-user-projects-by-user-id-pairs
[db-conn userid & {:keys [sort filters offset size]
:or {sort "asc"
filters nil
offset 10
size 10}}]
(println [db-conn userid sort filters offset size]))
Use it (note missing {}):
user> (get-user-projects-by-user-id-pairs :conn :uid :offset 9)
[:conn :uid asc nil 9 10]