In the C-based languages that you apparently familiar with there are several control statements that aren't expressions like for, if, while, and do. For example, here is how you can use the if statement in C:
if (a >= 0) {
printf("Positive");
} else {
printf("Negative");
}
The if control statement in C can only be used to induce side effects, such as printing to stdout, it can not be used for functional programming. On the other hand, Lisp is an expression oriented language, so expressions such as if can be used as values for functional programming:
(if (
The same principle applies to the let statement. It is an expression so that you can use it without ever resorting to creating mutable state or inducing side effects. If you allow emacs to automatically handle the nesting involved with your statements, then there it shouldn't really be an inconvience. However, when you need global mutable state use def:
(def x (Math/sqrt (+ 256 (* a a)))
(def y (Math/log (- (+ a b) (/ 1 (+ (* b b b))))
(prn (* 2 (+ x y)))
Clojure encourages good practices like avoiding local mutable state. That said, if you want to you can always create your own local environment to declare local mutable state:
(defmacro defun
[name args & code]
`(defn
~name
~args
(with-local-vars [~(symbol 'e) {}]
(let [~(symbol 'def*!)
(fn [p# v#]
(var-set ~(symbol 'e) (assoc (deref ~(symbol 'e)) p# v#)))]
~@code))))
Here is an example of code like yours using this macro and a local environment called e:
(defun func
[]
(def*! 'x (Math/sqrt (+ 256 (* a a))))
(def*! 'y (Math/log (- (+ a b) (/ (+ a (* b b b))))))
(* 2 (+ (@e 'x) (@e 'y))))
You could probably make that look nicer using macrolet and by definining other new macros and operations.