Parenthesis - defining the structure of Clojure code
Clojure uses parenthesis, round brackets
(), as a simple way to define the structure of the code and provide clear and unambiguous scope. This structure is the syntax of symbolic expressions.
The Parenthesis hangup
With support for higher order functions, functional composition and threading macros, Clojure code typically uses fewer parens than other languages especially as the scope of the problem space grows.
All languages use parens to wrap a part of an expression, requiring additional syntax to identify the boundaries of each expression so it can be parsed by humans and computers alike. Clojure uses a single way to express everything, homoiconicity, where as most other languages require additional syntax for different parts of the code.
This structure of Clojure code is simple to parse for both humans and computers. it is simple to navigate, simple to avoid breaking the syntax of the language and simple to provide tooling that keeps the syntax of the code correct.
Working with Parens
Clojure aware editors all support structured editing to manage parens and ensure they remain balanced (same number of open and close parens).
Homoiconicity and Macros
Clojure is a dialect of LISP and naturally was designed to be a homoiconic language. This means the syntax for behavior and data is the same. This greatly simplifies the syntax of Clojure and all LISP style languages.
The Clojure Reader is a parser that reads in data structures as expression, rather than parsing of text required by other languages. The result of parsing is a collection of data structures that can be traversed (asymmetric syntax tree - AST). Compared to most languages the compiler does very little and you can consider Clojure really does not have a syntax.
Code is written as data structures that are accessible to the other parts of the code, providing a way to write code that manipulate those data structures and generate new code. In Clojure this type of code is called a macro, a piece of code that writes new code.
- Inspired by Beating the Averages by Paul Graham
Example: Function invocation
(function arg1 arg2 arg3) ;; => value returned
- a function call is just one expression, called a "form"
- function calls can be constructed (cons function-symbol list-of-args)
- functions can be arguments to other functions (higher order functions)
- simple syntax to parse - everything between two parentheses is a self-contained expression.
- fewer parens due to high order functions, composition and threading macros
function (arg1 arg2 arg3) => some result
- a function call is no longer a single form and have to pass the function name and the argument list.
- syntax is complex and requires additional syntax rules to define a function call
- code generation is very complex
- same number of parens as Clojure or possibly more (no direct support for higher order functions)