Functional Programming with Clojure. Prototyping and Delivery
- 09 Aug 2022
- Articles
Clojure has four types of mutable references. The first one is Atom, Ref, Agent, and Vars. Vars are a little bit different from all others because atoms, refs and agents are shared memory and they share the values between threads, but Vars have their own value in each thread. The main difference between Refs, Items and Agents can be shown as follows. They can be synchronous and asynchronous, and they can be coordinated and autonomous. So, Ref is synchronous and coordinated. Atoms are synchronous and autonomous, and agents are asynchronous and autonomous.
Coordinated here means that we can run some set of mutations as a transaction. For example, like in a bank account, if we want to transfer some money, we really want to make sure that if money was deducted from one account, it is actually added to another. And if some operation failed, everything should fail. Autonomous is independent and synchronous will wait for things to be done. Asynchronous will immediately continue to perform the next part of the program and run it in the background.
It is short for the Read-eval-print loop. It is a program that said, “Read an input”, then “Evaluate an input” and “Print the result”. These three functions are running in a row. It was first introduced in LISP and now we have REPL kinds of things in many different programming languages, but in Clojure, it's a better version. So, in Closure we can for example run all our projects in REPL, Clojure has some startup time that can be significant, but not very much. But when we load everything in REPL, the reload is very fast, it's almost instantaneous. We can just reload the code that we changed and have everything work with a new version of our code. No need to compile everything, no need to restart, we’re just swapping in the new code. There is also extensive IDE support for the REPL, almost all Clojure-specific IDEs have different kinds of REPL support. It can be local or remote.
As mentioned before, we can run the entire project in REPL and the changes can be applied without any restarts and delays. Also, we can just send some part of the code segment from the file directly in REPL. In this case, the REPL may run your project code or it may be just a newly loaded REPL, so it doesn’t matter, maybe your code segment will modify the behavior of your running project or maybe you just want to see what the result of an expression will be.
REPL is also very useful to run unit tests, you can simply start your REPL and whenever you need to run your unit tests, you just can use your command and the unit test that you want is already running. You also can add some custom REPL commands. For example, if you just open your REPL and you want to start a project, startup, or command, it can have a lot of different components, etc. But most of the IDEs have an option to create a custom command, so you can just have some hotkey to start your project.
One more notable feature of Clojure REPL is a remote session. You can send your code to be evaluated on another machine. For example, it can be a development server, some kind of sandbox server, where this remote REPL session is running and developers can connect to this session and have some code executed and get some values from the running project in the deployed environment. It can be useful.
The next major thing that we discuss today is Clojure and Java interop. So Clojure runs on JVM, as Java. Actually, Clojure is partially written in Java. Most of its core is written in Java and everything else is released in Clojure. So what it gives us are the libraries.
Java notably has a lot of libraries. Basically, there is a library in Java for everything you need. You can easily use the Java library in Clojure and it is almost as simple as using Java libraries in Java, but you are using it from Clojure. In some cases, it might even be simpler.
So also, you can use Clojure from Java. It's a little bit more rare use case but you still can do it. There are a lot of ways how you can achieve this. Here’s just one example of this.
You can directly use Java libraries from Clojure. Why closure is so popular and why it is so good, is because you have all these libraries but also Clojure has a lot of wrappers or Java libraries. It is useful because your Java libraries aren't very functional and you have a lot of states, a lot of side effects and functions, all these Java objects. But it is really easy to write a wrapper that will expose some pure function API and it is much faster than creating your own library in LISP. There are lots of wrappers already developed and you can use all these libraries.
As for the structural editing we have two options in Clojure and other LISPs. There may be some more, but the major ones are Paredit and Parinfer.Paredit fixes the structure of syntax and codes - data structures that resemble the Clojure syntax. And for example, you cannot delete the brackets if there is something in it, so it will keep all forms balanced. You cannot delete a closing bracket without deleting an opening one. Just won't let you. There are some special comments to rearrange brackets just like this. In Parinfer you don't have to write your brackets. Parinfer tries its best to infer where they should be.
The things to note about structural editing. Talking about helpers, if you're working with the team, it's highly recommended to use the same one because it will mess up the formatting. For example, if someone uses Parinfer and he opens a file, it will immediately reformat it. So not a thing that you want to work on when not everyone else is using Parinfer, it will be hard to have a clean deep history and to remove the changes that are just formatting.
And, finally, we’d like to discuss the Clojure idioms. This resembles the correct way of using Clojure and using functions and syntax the way it was intended by the creators. And it makes your code more readable to you and others. For instance, if you need to contact the result of mapping some function over some sequence, you have the actual function in Clojure that does exactly this. So you don't need to write a code like this. Similarly, when you are filtering some sequence, you have another type of filter that does not keep the elements that give the true result of applying an element to the predicate function over here but removes it.