Hacker News new | ask | show | jobs
by namelezz 4120 days ago
Dynamic typing and parentheses are what keep me away from Clojure or Lisp like languages. How can I get over them?
7 comments

There aren't many languages that have radically fewer parentheses than Clojure / LISP.

Clojure:

    (defn blub-extra [a b]
        (blub (inc a) (inc b))) 
8 parens + 2 brackets

Scala:

    def blubExtra(a: Int, b: Int): Int {
       blub(inc(a), inc(b))
    }
8 parens + 2 braces

Java:

    Integer blubExtra(Integer a, Integer b) {
        return blub(inc(a), inc(b));
    }
8 parens + 2 braces

Ruby:

    def blubExtra(a, b)
        blub(inc(a), inc(b))
    end
8 parens

Python:

    def blubExtra(a, b):
        return blub(inc(a), inc(b))
8 parens, one colon

C:

    int blubExtra(int a, int b) {
        return blub(inc(a), inc(b));
    }
8 parens + 2 braces

It's roughly the same numbers of brackets (or equivalent) in Clojure, Scala, C and Java. A bit less in Python and Ruby.

Scala example is wrong, wouldn't even compile; should be:

    def blubExtra(a: Int, b: Int): Int = blub(inc(a), inc(b))
The original would need an equals thrown in there, otherwise it's procedural syntax (IIRC, has been deprecated or will be in the next release) which has a return type of Unit, thus not compiling when specifying a return type of Int.

    def blubExtra(a: Int, b: Int): Int = {
      blub(inc(a), inc(b))
    }
Thanks for the typo spot. I've not written Scala for about 5 years. Unfortunately I can't go back and edit my post.
You can do it in Ruby with 4 parens, or even 0 if inc is really just +1. I wouldn't ever write it with so few but if we're really going down this rabbit hole then we ahould get it right.
Please feel free to contribute a Ruby example (as long as it's normal and idiomatic, this isn't a competition)! I've not written Ruby for years.

(inc was just a random function for the sake of showing invocation so it's not really fair to use operators)

I think it's the positioning of them that makes them stand out to people, plus the fact that they tend to bunch up at the end of things.
Of course, I know. But most people express it as 'too many parens', without (I believe) actually thinking too hard. I was just trying to provoke a few thoughts!

The position of parens is something to get used to, but then again so is Python's indentation, Scala's type system, etc etc. Every language has something unique to get used to.

as someone already pointed out on twitter:

F#:

let blubExtra a b = blub (inc a) (inc b)

4 parens.

Usually we would write it as:

let blubExtra a b = inc b |> blub (inc a)

which 2 parens less, but at the cost of using the (very idiomatic) pipe operator.

If you truly have been shunning Lisps because of parentheses I can only tell you that it will start to make sense once you see that they're just lists and that the fact that everything (including your code) is some variation of a list means that you are now (sometimes in the background) able to modify everything as if it was a list.

S-expressions are uniform in that they all look the same way. This means you have one form for literally everything in the program and you will have no problem parsing that form. It makes reading, mentally parsing and editing easier because everything is neatly delimited by parentheses.

In terms of readability, do you have any particular difficulties parsing the following code?

    (define (sum/recursive lst [sum 0])
      (if (null? lst)
          sum
          (sum/recursive (rest lst)
                         (+ sum (first lst)))))
We are defining a function, sum/recursive, that will take a list and return the sum of all the numbers in that list.

We use a default value of 0 for a function parameter called sum to store the sum. When the input list is empty ('(null? lst)' returns true) we return that variable. There is no return keyword, we just specify that variable and it will be returned.

If the list isn't empty, we apply sum/recursive on the rest of the list and as a second parameter we add the first number in the list to the already accumulated sum ('(+ sum (first lst))'). Using matching parens in your editor will make it obvious when you have matched the right amount of parens.

It should be noted that Racket, this Lisp variant, uses '[]' as well to delimit certain parts, for readability.

Lisp's super power (homoiconicity) comes at a trade-off of reduced discriminability. A good syntax highlighter and well-indented code (like you provided) reverses this effect considerably, but imho, it still takes a lot of getting used to for beginners.

In any case, the bigger part of the being productive in a codebase is to figure out how it encodes the domain, what idioms and patterns are favoured etc. So sometimes, it's just not worth adding another source of aggravation for "new arrivals".

I disagree with regard to Clojure. IME, the fact that it's a lisp doesn't seem as hard for noobs to grasp as immutability by default, or pickup up the functional programming mindset. We usually go for folks who use Emacs, so they may already have an indoctrination.

When you compare Clojure to other lisps, I think you'd agree it's more discriminable, given the plethora of literals that dont use parenthesis: vectors [1 2 3] sets #{1 2 3} maps {:key "value" :name "Bryan"}

I've used clojure for some weekend hacking and have found that the biggest challenge is feeling like my code is non-optimal. I can get stuff to work and could build real stuff in clojure, but I hesitate because I feel that it's not necessarily idiomatic.

I guess I need a clojure mentor for the first non-trivial project. There really isn't that much idiomatic web-related code out there to read, and with clojure there is also quite a lot of variation in how systems get designed (b/c of the power and expressiveness available).

This is not an excuse, just noting the factor that has turned out to slow me down. If i had more leisure time I'd just power through and build stuff until I arrived (through trial and error) at my own sense of best-practices / design tradeoffs. That has yet to happen but will someday.

> S-expressions are uniform in that they all look the same way.

If you treat the language as a user interface to your computer, shouldn't things that do different things have different appearances, to help you distinguish them at a glance?

The braces become "invisible" after some training. Different elements of the program are distingishable. "+" is different from "if", which is different from "42".
It's all about people, so there are likely not cut and dried answers, but I know I have always found C style code a bit easier to read than Lisp style code. Part of this may be due to other conventions, such as larger indents in C code.

Like you say, with training, the differences are probably not that big a deal, but I don't think they are something to completely ignore, either.

the point is to free yourself to focus on semantics rather than endlessly learning syntax
Could you clarify? In what language(s) is the learning of syntax "endless"?
I think Clojure is actually a lot safer by default than most people expect for a Lisp. Check out this comparison I wrote up between C#(or Java), F#, Clojure, and JavaScript on common edge case safety. http://deliberate-software.com/programming-language-safety-a...

In most ways I care about, Clojure is actually safer than C#. Now, if you're coming from an ML HM language, sure, you'll be taking a step back perhaps.

Clojure is horribly unsafe. I spent a year writing Clojure and half the time we were fixing bugs where someone had wrapped a map in another map or changed the shape of a data structure. For all that's worth, I'd rather use Javascript. To add insult to injury (the amount of wasted time on something that could be easily checked by a compiler and a sane type system) there's all the bad habits you acquire and the general lack of confidence in your code, which took a good six months after that to cure. I'm sticking with types now, thank you very much...
Sounds like you needed Schema, which can ensure the shape and contents of a map as :pre and :post assertions in every function you annotate, then be turned off for production use. https://github.com/Prismatic/schema

That's why I come back to Clojure every time, if I need anything, it's available as a library. Need better typesafety? Just add in what you need with a macro or two. (Schema is not much more than that).

That's not really possible easily with Javascript, and Javascript has a lot more unsafe by default edge cases than Clojure. I'm not sure why if a mostly safe dynamic language bothers you, you'd now prefer an extremely unsafe dynamic language?

Lastly, types only give you a single dimension of safety, is this that exact shape, and ensuring I don't put a square peg into a round hole: both of which Schema adds in easily. Clojure is ALSO safe along many other dimensions, like thread saftey, immutably, null reference exceptions, etc.

80% of the problems I had to deal with (in a live, real-life Clojure system that had to be maintained and supported, not a hobby-happy-slappy-project-thing) were caused by putting square pegs in a round hole. You're telling me I need to get a macro-based library and add :pre and :post conditions that check the structure of a map, as opposed to using a language with types?! Really??? Oh, and guess what, I am sure there is a Javascript library that does that too...
I agree with most of what you said, but

> Lastly, types only give you a single dimension of safety.

Dependent types can express a huge number of things, and are often limited only by your ability to describe what exactly you want.

Absolutely right, I was just comparing with the dominant Java or Python.

Dependent typing is some cool stuff, I'd love to see more of it.

I have been following Idris, quite cool.
Do you use a dependently typed language in production?
I don't really use anything in production, I am just a learner right now :)

There are some people using some of extensions for Haskell inspired by "dependently typed" systems in production.

Dependent typing is an active research topic right now, and I doubt any fully fledged dependently typed language is used "in production".

However, I was just pointing out that it is possible for types to express a wide variety of things that we do not normally associate with them. Even without full dependent typing, Haskell types are wonderfully expressive and powerful.

Oh, god, thanks for reminding me about the whole agent/atom/ref hell - another reason I'd rather erase the whole Clojure experience from my brain altogether...
Jesus, maybe you were not able to read a clojure book befor you started. Its amazingly simple to explain how to write thread safe code with clojure, and pretty easy to add concurrency safly.

There are 3 concepts that are all diffrent, and a couple yours ago core.async was added. All in all thats a amazingly simple and powerful combination. Everybody I have heard is pretty happy with these tools. What is your problem?

Agree to disagree. I do not enjoy the impossible to prove defensive coding around threads and callbacks. The core.async library made threading in Clojure for me to now be simple and pleasant compared to difficult to test mutexes and deadlocks.
You're the first person I've seen that is vocal about having a truly bad experience with Clojure. Even from other people who prefer strongly typed language, I've mostly seen them expressing it as "not their taste", rather than actively dislike it. Do you mind sharing your experience and what was wrong with Clojure? You put js as an alternative, but in term of typing, js is borderline one of the worse type system out there.
Not as an alternative, but as similarly unsafe. It's funny to see how all the Clojure advocates bash Javascript...
I don't know where this trick come from, but I've read it a long time ago, and you might find it helpful.

Lisp languages in fact have the same(ish) amount of parentheses as any other language, the trick is that the placement slightly differ: the opening parentheses come before the function name, rather than after it. Ie foo(bar) => (foo bar) . foo(bar(baz)) => (foo (bar baz)).

This trick is very helpful, but I would argue that there are still a lot more parentheses due to almost everything being written as a function in lisp. When I started programming in Racket arithmetic and comparison expressions tripped me up frequently. My tip to beginners in this area would be to use the dot-notation first, then switch to the standard way once comfortable. for example:

    (20 . > . (10 . / . 5))
may be more clear than

    (> 20 (/ 10 5))
parentheses, at least in my exp, just fade away after a while.. there's core.typed which is static typing for clojure [0]

[0] https://github.com/clojure/core.typed

Also Prismatic's schema. It achieves some of the same goals for why you would want strong typing:

https://github.com/Prismatic/schema

+1 for Prismatic Schema. It's not only an awesome way to validate your maps, but it is an extremely good way to write documentation.
It's also very worth looking at Typed Racket and Racket's contracts system!

    [1] http://docs.racket-lang.org/ts-guide/
    [2] http://docs.racket-lang.org/guide/contracts.html?q=contracts
Thank you for the link. I will look into the core.typed.
Time.

With Clojure/Lisp languages you are basically starting from the beginning. Most of the approaches you would take to constructing your applications don't work. It just takes a lot of practice for you to recognise patterns and then both the typing and parentheses will seem natural.

Compile-time typing really is valuable, IME. Maybe you should look at Shen?

(Can't help you on the brackets. I mostly stick to Scala myself)