I spent couple of months learning Scala and got overwhelmed by number of different constructs it had,
After looking at partial functions, partially-applied functions, pattern matching, actor syntax,
I gave a thought to learning Clojure which doesn’t have too much in terms of syntax but looking at the how Java inter-operability is handled, it looks very difficult to get use to.
for example: things like doto, new and .
Every time I start typing a function, there are times I write (1 + 2)
instead of (+ 1 2).
It looks like I will have to completely forget how I write regular programs to get use to this syntax.
Also finding it difficult to deal with side-effects that do and let form have.
Aren’t they similar to what functions with void type are ?
Things like registering a callback,things normal I/O, how can such things be side-effects free.
Till now all I really liked about Functional Programming is that,
immutability makes concurrency easier when dealing with shared data,
functions as first class objects and having higher-order functions,
apply,map and reduce with functions and lists
.
I wanted to know, are there practical benefits I will get once I am use to this syntax ?
I f I continue to learn Scala and avoid using var, and always use recursion instead of loops.
I can do all the above things and still write understandable code.
6
Lisp syntax is just that: syntax. It has nothing to do with semantics. In other words, the syntax (ideally) shouldn’t affect how you write programs; if you feel like you have to “completely forget how you write regular programs”, it sounds like you’re having difficulty with the semantics, not with the syntax.
Practical benefits of Lisp syntax include:
-
consistency. Function/macro/special-form followed by its arguments. No operator precedence issues. Grouping is usually explicit.
-
easy to parse and unparse. This makes it easier to write source code tools, such as static analyzers and code browsers.
-
extensibility
- user-defined macros. Users can extend their Lisp system’s syntax.
- system-defined syntax. New special forms can be added to a Lisp system without having to worry about how their syntax interferes with existing forms.
You also threw in a couple of questions about side effects and functional programming. Those aren’t related to Lisp syntax and should really be asked in a separate question.
8
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.
2
These are matter of taste. I have converted gradually over the years to preferring Lisp syntax over Java syntax. Why? Good question. Can’t really pinpoint a single reason, but here are some thoughts.
Lisp syntax is very simple, almost trivial, albeit maybe bit strange at first (before you unlearn some old habits). Trivial syntax makes it easy to programmatically mangle Lisp code. Code is data is code. Make macros and DSL way more easier and effective. “Lisp is a programmable programming language.”
This gets bit philosphical, but I strongly feel that Lisp syntax is closer to “true nature of programs”. Structure of programs is not linear — it’s more tree/network like. Java point of view is more linear, line oriented. Lisp POV is hierarcial and tree oriented. Those parenthesis inside paranthesis is just a way to write trees.
Thinking more in tree oriented (rather than line oriented) way enables new ways of thinking, editing and working with code. For example some powerfull tools such as Paredit, which demonstrates some concepts which might not make much sense in Java syntax, such as barf and slurp:
slurp:
(foo (bar |baz) quux zot) --> (foo (bar |baz quux) zot)
barf:
(foo (bar |baz quux) zot) --> (foo (bar |baz) quux zot)
Code is a tree! 🙂
The principles of the functional language like lisp or clojure are very useful for writing self modifing code.
So for adding some values you have only to generate a instruction with a + at the beginning and the numbers after that, in an imperative language you have to add each additional value manually.
The thing is, languages like lisp or clojure don’t really have any syntax, ist only a description of a tree structure as text.
19