Syntax
The syntax threw me off too at first, but after working with it and reading about it I realized some things:
1. Lisp doesn't have operators, it only has functions, so stop thinking of +-*/
as operators and that will help with things.
Don't think:
(1 + 2 + 3 + 4)
or, even though it's correct:
(+ 1 2 3 4)
but:
(sum 1 2 3 4)
and it will feel more natural.
Likewise with -
, think more along the lines of:
(subtract-from x y1 y2 y3)
and that should help.
2. Method calls aren't that different!
Functions are the first thing in every Lisp form (the stuff between the ()
); since methods are functions, they come first! It's not really that different from Java's syntax though -- all you're doing is moving the starting paren to the front and swapping the positions of the object and method:
Java:
objectInstance.someMethod(arg1, arg2, arg3);
Clojure:
(.someMethod objectInstance arg1 arg2 arg3)
(You also lose the commas and semicolon!)
The (.someMethod objectInstance ...)
is a special form that gets expanded out into a form using .
: (. objectInstance someMethod ...)
. The latter feels weird because we're not used to thinking of the .
as actually doing something when we're calling a method/property in Java; the former is the idiomatic way of doing things in Clojure anyway (reference).
New is similar:
new namespace.Object(); // Java
(new namespace.Object) ; Clojure
"Difficult Things"
The "difficult things" you mentioned are actually there to make your life easier!!
You said in a comment that it was annoying to keep track of things when nesting expressions within expressions; the do
and let
forms are built specifically to help ease that difficulty.
Let's start with do
do
lets you call things imperatively, one after another, without nesting:
(do (.someMethod objectInstance arg1 arg2)
(.someOtherMethod objectInstance "foo" "bar" "baz")
(.aMethod anotherObjectInstance 0))
The behavior of do
is such that each of these method calls will be run in the order in which they're given and the return result of the last expression (in this case the return result of (.aMethod anotherObjectInstance 0)
) is what is returned from the do
block itself.
Each of these methods may or may not have side effects, and may or may not return a value (all of which but the last will be ignored).
Let
The let
form does the same thing as do
but it also gives you the ability to add local variables to the mix:
(let [foo "foo"
bar "bar"
arg1 (generateValueFrom foo)
arg2 (generateValueFrom bar)]
(.someMethod objectInstance arg1 arg2)
(.someOtherMethod objectInstance foo bar "baz")
(.aMethod anotherObjectInstance 0))
Again, the method calls will be run step by step in order, only this time they use the local variables defined in the let
's bindings (the stuff between the []
), and the last call's return value will be returned as the let
's return value.
let
is handy for breaking a bunch of nested calls down into something more sane; e.g.:
(reduce someCombiner {} (filter keepXs (map someTransform ys)))
becomes:
(let [txd (map someTransform ys)
xs (filter keepXs txd)]
; do the last call in the let's body so that
; its return value is what gets returned
(reduce someCombiner {} xs))
Is The Synatx Worth It?
You bet your sweet bippy it is!
The Lisp syntax allows the language to have macros. Macros are functions that run at compile time that rewrite the code. What this means is that you can take code that is repetitititve or complex and write a macro that is simpler to call, and that macro will write the complex version for you.
For example, say we have the following do
block:
(do (.someMethod objectInstance arg1 arg2)
(.aSillyMethod objectInstance "foo" "bar" "baz")
(.aSadMethod objectInstance "alpha")
(.anHappyMethod objectInstance 1 2 3)
(.someOtherMethod objectInstance \a \b \c)
(.aMethod objectInstance 0))
We have to repeat the same object instance six times! That's annoying. The doto
macro can make things a bit lighter for us typing-wise:
(doto objectInstance
(.someMethod arg1 arg2)
(.aSillyMethod "foo" "bar" "baz")
(.aSadMethod "alpha")
(.anHappyMethod 1 2 3)
(.someOtherMethod \a \b \c)
(.aMethod 0))
The doto
macro allows us to specify the object instance once, up front, instead of having to repeat ourselves in the code later on -- it rewrites the expressions we give into the do
block shown above. (This particular macro is similar to the with
syntax some languages support.)
Now, this is a really simple example, but just imagine what sorts of coding tasks you could automate! And that's where the power of Lisp's macros comes in: you can write functions to make the programming language write your code for you!
Side Effects
You can still have side-effects in Clojure code, especially if you're doing interop with Java OOP since that OOP has side-effects. If you use more of the core Clojure code than Java, you'll not have as many side effects because it'll be more purely functional.
A lot of what I've found in my functional programming experience is that it's about partitioning the side-effect causing code from the purely functional code. You can do this in pretty much whatever language you're using (even Java).
Comparison to Scala
I've not used Scala, so I can't help you there. But hopefully the above was informative.