The best way to think of Lisp syntax is not to think of it as a syntax, but the lack of a syntax. In other languages you have certain rules on how you can write your code so that the compiler can parse the code and internally assemble it into a tree-structure called the AST (abstract syntax tree). In Lisp you just skip the syntax and straight-up write the AST directly.
Take for example the math expression
(3 * [2 + x])/(7^2)
you need to know the order of operations in order to evaluate it and you have to jump around in the expression. Writing the same expression as an S-expression:
(/ (* 3 (+ 2 x)) (^ 7 2))
(Scheme has no ^ operator, but you could define it if you wanted to) It becomes easier to see if we write it two-dimensionally:
(/ (* 3
(+ 2 x))
(^ 7 2))
You can now see the tree-structure, and this is also why Lisp code is usually indented this way. Let's now define the range function in Scheme.
(define (range min max)
((= min max) '())
(else (cons min
(range (+1 min)
(There is no error checking to make sure that the initial arguments are valid, but neither does you OCaml code) This is pretty much a 1:1 translation of your OCaml code. We have a conditional (could have used an if, but cond is more common) with two cases.
Note that the lack of syntax means that we have to explicitly state that we want to have a conditional check, whereas it was implicit in your OCaml code because the syntax filled in that information. The same goes for the cons: there is no list comprehension syntax, so we explicitly have to say that we want to construct a list.
So if we have to be explicit about everything, then what is the point of this non-syntax? Well, I call it a non-syntax, but of course it is a syntax, a homoiconic one: everything looks the same. The cond operator for example is not a function, but it is used the same way as a function. For one, this means there are no surprises, if you know this one rule you know all of Lisp's syntax. The other big thing is that adding your own operators is possible and thus you can expand the language using the language itself. It's all just transforming one tree structure into another.
Let's take local binding as an example: We want to bind some value to a specific environment context and then throw them away again. In Scheme the only operator that can construct a new environment is the lambda λ.
((λ (foo bar baz)
(display baz)) 1 2 3)
WTF is this shit? We are creating a new anonymous function with the λ that has three parameters (foo, bar, baz). This expression is surrounded in another pair of parentheses because we are immediately calling it and we are passing 1, 2 and 3 as its arguments. Now the function body can use these values to do anything it wants with them. This code is ugly, unidiomatic and easy to get wrong. How about we invent a new operator?
(let ((foo 1)
Even if you have never seen Scheme in your life you can guess what this is doing. The let operator is part of the standard, but you could just as well retrofit it as a macro so that the let-tree gets rewritten into the define-tree above. In fact, this is usually how a Scheme implementation is going to implement the let operator.
This is a general-purpose operator, but you could just as well invent a series of operators for your particular problem domain. Then you can write your program in this domain-specific language instead of messing about with the entire underlying language.
Reading this code makes me feel laborious as I have to stop multiple times while reading the code to understand what needs to be evaluated and what gets paired with what. I guess with practice I should be able to grasp the structure more easily but maybe I'm doing something wrong.
Common Lisp is a rather ugly Lisp, but prefix-notation is something you have to get used to in any Lisp. It's weird at first, but the precision and unambiguity is a huge win. I do a lot with math and sometimes standard notation becomes so ugly that I rewrite it in S-expressions on paper for myself just so that I can make sense of it. It's weird, but in a good way.