Clojure Functions - Definitions, Distinctions, and Examples

There are many ways to define a function and parse arguments. In this chapter, we'll learn more about function definition, destructuring and some helpful higher-order functions.

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

This lesson preview is part of the Tinycanva: Clojure for React Developers course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to Tinycanva: Clojure for React Developers, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course Tinycanva: Clojure for React Developers
  • [00:00 - 00:09] So far we have seen the usage of defn to define functions. But most of our examples were contrived and not close to real world use cases.

    [00:10 - 00:24] At work, the functions we define might need to handle a variable number of arguments, or they might need to destructure a large map past ten as an argument. In this chapter we learn various aspects of defining functions in closure.

    [00:25 - 00:33] The examples in this chapter are coded in the first project.fndf's namespace. Can you figure out the disk path of this namespace?

    [00:34 - 00:47] Multiarity functions let you accept different number or type of variables. They work by defining different execution paths for different arguments and are generally used to provide default arguments.

    [00:48 - 00:57] Multiarity functions are conceptually similar to function overloading. Anonymous functions are functions that have no home, i.e. are not namespace qualified.

    [00:58 - 01:13] They exist only at the point of definition and are useful for a variety of applications, like defining callbacks or simple predicates. There are two ways to define inline functions, using the fn macro or using the hash parenthesis shorthand.

    [01:14 - 01:32] fn and defn have similar signatures except the function name is not present while using fn. With the shorthand we can completely skip the argument vectors and access the arguments using the percentage n sign, where n is the index of the argument we want to access starting with 1.

    [01:33 - 01:43] If you need to capture only one argument you can skip the index and simply use the percentage sign. Anonymous functions are helpful while working with sequence operations like map and reduce.

    [01:44 - 01:51] We will see them in action soon. Functions defined with defn or fn can follow an alternate signature to specify inline documentation.

    [01:52 - 02:01] Variable Addity or Variadic functions lets you capture any number of arguments as a list. We have seen this function already in the first project.code namespace.

    [02:02 - 02:10] Variadic functions use ampersand to signify the variable number of arguments. The rest variables is available in the function scope as a list.

    [02:11 - 02:24] If we call f with 5 arguments f1 to 3 4 5, then add 1's value will be 1 and rest will be a list of 4 elements to 3 4 5. We have so far called functions by passing the arguments manually.

    [02:25 - 02:34] For example, f defined above is called by typing f1 to 3 4 5. But what if we wanted to call f for a list of n integers?

    [02:35 - 02:43] What if these arguments were stored in a variable? The apply function lets you unpack sequences and use them as function arguments .

    [02:44 - 02:57] Apply f list is same as calling f with the values of list unpacked and sent in as arguments. In UI programming frameworks like React, it is common to pass down props to child components.

    [02:58 - 03:05] It is also fairly common to pass configuration maps as function arguments. Sometimes you might need only a small part of the argument.

    [03:06 - 03:13] The structuring helps you precisely pull out the required pieces. Let us suppose that the function takes a 3 element vector as an argument.

    [03:14 - 03:25] This vector is switched directly from a database and holds the name, age and height of a user. If we wanted to get name, age and height with what we know so far, we could simply write something like this.

    [03:26 - 03:32] The first, second and last function pull out the first, second and last element of the vector. This doesn't spark joy.

    [03:33 - 03:41] With sequence destructuring, the same code can be rewritten as so. The order of elements is important while these structuring sequences.

    [03:42 - 03:52] If you don't care about an argument, say age, you can use an underscore in place of the variable name. You can just let it be age 2, but if a variable is not used in a function the l inter might complain.

    [03:53 - 04:04] You can also use the underscore prefix like underscore age instead of just an underscore. The underscore prefix can be used with normal functions too, i.e. where destructuring is not being used.

    [04:05 - 04:13] You can also use destructuring with lead bindings. If you want to destructure and do not pull out all elements of the sequence, the tailing elements are ignored.

    [04:14 - 04:24] Let us assume that we have the following worldview of a user, name, John Doe, age 29 and so on. And we pass the user to a function that prints their name and age.

    [04:25 - 04:34] With all that we know so far, the definition of this function might look something similar to. This seems reasonable, but can get out of control as the function assumes more responsibility.

    [04:35 - 04:45] Also what happens if you want to use the age or name more than once? You can call the keyword as function at multiple places, but a lead binding makes more sense.

    [04:46 - 04:51] The example above is more realistic. With map these structuring, the function can be rewritten as so.

    [04:52 - 05:01] There is still some repetition, the variable age and the map key age appear twice. This can be helpful if you want to call map key age something else in your function scope.

    [05:02 - 05:12] But if you want the variable names to be same as map keys, you can further rewrite the same as follows. Notice the use of keys in destructuring.

    [05:13 - 05:24] We can go one step further by getting rid of lead and destructuring the arguments directly in the function arguments vector. In the real world your map representing your knowledge about a user might not be perfect.

    [05:25 - 05:31] It might be missing some information. For example, our user John doesn't have any information about time zone.

    [05:32 - 05:41] The structuring lets you assume defaults if the required information is missing in the map. We can use the OR form for destructuring to assume defaults.

    [05:42 - 05:52] All these structuring constructs work at any level of nesting. If you have a 2x2 matrix and want to destructure all elements, you can just nest these syntax.

    [05:53 - 06:03] We added an extra line break in the example for better readability. As you become used to these syntax, you will be able to read and write a destructuring like this in a single line.

    [06:04 - 06:14] We can also destructure a nested map in a similar way. The nested map destructuring is a combination of destructuring techniques used in print user 4 and print user 5 function.

    [06:15 - 06:22] It is ok if you don't get this in the first go. As you work with functions, you will develop an eye to read complex destruct ures.

    [06:23 - 06:31] We have referred to many functions such as def and when, which are in fact macros. Macros rewrite your code at build time.

    [06:32 - 06:45] For example, def and macro converts your function definition into a variable definition using def. We will not study about macros in detail, but we should make clear that some symbols that we refer to as functions might be macros.

    [06:46 - 06:57] Functional composition lets us apply multiple functions to an argument in series. The comp function takes multiple functions and returns a new function which is an effective composition of inputs.

    [06:58 - 07:09] In this example, the composed function inks square, first squares the input and then increments it by one. Comp PQR arguments is same as P of Q of R of arguments.

    [07:10 - 07:22] Pascials or partial functions reduce the argument count of a function by setting defaults. The add function takes two argument, but the add 5 partial sets the default argument on add as 5.

    [07:23 - 07:31] In this chapter, we learned about various types of function definitions. We then moved to argument destructuring and made a distinction between function and macros.

    [07:32 - 07:46] Destructuring makes code concise, but at the same time hard to read for beginners. With knowledge of various destructuring methods, you would be able to read publicly available code with more reads and over time write more efficient and concise closure.