Hacker News new | ask | show | jobs
by hacker_9 3311 days ago
Thinking of Clojure as a subset of JS is a new one. It's more accurate to say JS is a badly implemented Lisp! These are the main reasons Clojure works so well:

1. It is a Lisp: The only type is the 'list', and in Clojure you also have 'maps' too (basically a dictionary in JS). That's it. Any object in JS is a map in Clojure, which keeps everything nice and simple. The main power of this is you only ever need to support lists or maps in your functions, and then your functions work for every data structure you ever create! For example clojure.core ships with 'get-in' 'assoc-in', 'diff' etc, all functions that work on your structures no matter how complex.

2. It is a functional language: JS is imperative, meaning you have to fit your ideas to the language, breaking everything down into for loops and if statements. With Clojure you fit the language to the idea, and think in data transformations instead. Then you create a set of functions that deal with that problem specifically, all wrapped up in single namespace. For example a 'tilemap' ns that contains a set of functions for converting to/from screen and grid coordinates. Or getting the neighbours of a specified tile from the 2d array. The end result is a custom DSL that reads almost like English.

3. It is 'immutable by default': All data structures are persistent. The problems you have with scaling in JS and Python is all because of mutable local and global state, as it becomes very difficult to know what is changing what. Clojure doesn't have this problem, as values can't be edited, only new ones returned. There is still mutability of course, but it is restricted to maybe 10 locations in your codebase. The mental burden this lifts from your shoulders cannot be understated.

4. It has a REPL: Let me ask how much effort you think building a HTML editor is? Or the UI designer in Visual Studio? With Clojure, I can just make edits to the GUI code on the fly and press F10 and voila the changes appear in-app. Instant editor, for free. This is what i was doing the other day; editing my GUI code (re-arranging controls, changing positions and sizes) and then immediately inspecting the result in the running program. The productivity gains are huge when you remove the laborious 'write code, wait for build, navigate to place in the app, finally test your changes, find error, end program, change code, repeat' cycle from your workflow.

5. Macros: Lisp macros are just Lisp code again. Literally the whole language available to you at compile time. The clojure.async library for example uses this to provide Go style channels for async programming. It means you can write code that looks great, and still compiles down to something performant at the end.

1 comments

Thanks for the response! +1! Let me take a stab at these points, bearing in mind that I'm not a JS fan or a Clojure opponent; just playing devil's advocate for my own learning purposes:

1. I accept that there are only lists and maps and primitives in Clojure, but I think the same is true of JS. The problem is that functions depend on these map/list structures having a particular schema, so in essence you can't just pass any old list or map to a function and have it do the right thing. I guess I don't see how Clojure and JS differ here.

2. What's stopping you from programming JS in a functional style? It's very possible to avoid `for` in JS (not sure what Clojure uses instead of `if` though). The JS standard library offers many functional features, and there are libraries dedicated to exactly this. I don't see how JS differs from Clojure here, except that it also supports an imperative style and "less is more".

3. If you're disciplined, you can do the same in JS or Python, but I accept that having rails in a language is better than resorting to discipline. The problems I mentioned with scaling JS and Python were related to typing (see point 1), not mutating state. I'm not sure what you mean by mutability introducing scaling problems, but Go is a mutable-by-default language which scales very nicely, both in terms of project size and parallelism (while I accept that immutability makes for easier parallelism, JS and Python's lack of parallelism have nothing to do with mutability except in the runtime). Clojure supports parallelism, which JS does not, so +1 for Clojure here.

4. I hear this a lot from Clojure proponents. The only REPL I've used is Python's, and I'm not a fan, but I understand that the Clojure REPL is a different beast entirely. I'll have to take you at your word here. Clojure beats JS here.

5. JS doesn't actually compile, so you can do all the metaprogramming you like. I also understand that JS has generators and coroutines, which should make async JS suck less; however, I haven't used them yet.

It seems like the biggest advantages Clojure has over JS are:

A) REPL B) Parallelism C) Rails + conventions > discipline

Thoughts?

True Javascript has maps, and you could use immutable.js for your structures. It also has functional capabilities too thesedays. But the difficult part of language design is actually making all these pieces work together fluidly. This is what Clojure achieves.

Immutable.js is great until I need to use a 3rd party lib that doesn't support the structures, so there is a copy involved and performance is lost and the code gets ugly. Functional constructs were botched into JS, so most code out there is a litter of imperative and functional, as well as containing lots of mutable global state. It's object orientated, losing much of the benefits that come from being functional first.

> I'm not sure what you mean by mutability introducing scaling problems

Side-effect free programming makes reasoning about your code easier. More here: https://stackoverflow.com/questions/214714/mutable-vs-immuta...

> The only REPL I've used is Python's

I've not used it, but the point is you can change code and upload it as the programs runs. Great for tweaking stuff. And because there's no global mutable state, you don't have to restart the REPL very often as everything just works.

> JS doesn't actually compile

Actually it is compiled on page load. Also JS has no macros, but it does have some metaprogramming capabilities at runtime (reflection mainly). Again though, having a feature doesn't make it any good. Clojure's consistent and easy API blows JS out of the water.

> Immutable.js is great until I need to use a 3rd party lib that doesn't support the structures, so there is a copy involved and performance is lost and the code gets ugly.

Don't use a library for immutability, just don't mutate. Easy-peasy.

> Functional constructs were botched into JS, so most code out there is a litter of imperative and functional, as well as containing lots of mutable global state. It's object orientated, losing much of the benefits that come from being functional first.

Yeah, multi-paradigm code is a pain.

> Actually it is compiled on page load. Also JS has no macros

If we're being pedantic, it compiles continually during interpretation as most popular implementations use JIT compilation. But my point is that macros wouldn't benefit such a language because there is no discrete compile phase.

> it does have some metaprogramming capabilities at runtime (reflection mainly)

I guess I'm not very familiar with metaprogramming. Most anything I can do with macros in a compiled language I could do at runtime in a dynamic language without special standard library or language support. I'll take you on your word that metaprogramming is useful and that Clojure does it better.

Anyway, I don't think you answered one of my questions--how is Clojure's IDE support? Can it do gotodef or tell you about the types of parameters, etc?

The problem is JS is built from the ground up to be mutable. For example one big difference in Clojure is that all mutation is done through the Atom, which is transactional, making it threadsafe.

I wasn't being pedantic saying JS is compiled, because it really is. Sure it's compiled to an IR though, which is then JIT compiled as needed.

Nice thing about macros is they are zero cost abstractions, so there is no overhead added by using them. For example in C# using Linq creates garbage, so you can't use it in a game engine else you get freezes every couple of seconds as the GC does a collection. With macros you can have the convenience while still compiling to something that is memory efficient.

As for an IDE, look at the Cursive plugin for IntelliJ. It's free for personal use and gives you a debugger, autocomplete, errors, goto definition, find usages etc.

The following responses are terse because I'm posting from my phone. Apologies in advance!

JS is not parallelized, so whether or not it's thread safe is a moot point.

Python is also compiled to a bytecode before interpretation; were still don't call it a compiled language.

In a JIT language, macros aren't significantly faster than runtime abstractions. In the latter case, you can always precompile your abstraction on program load at the expense of slower start times (think regex engines or precompile linq queries). The cost is negligible.

I can't seem to find anything on type documentation for Cursive. How can I ask it what type a function returns or what types it's arguments should take?

> JS is not parallelized

https://johnresig.com/blog/web-workers/

https://ponyfoo.com/articles/understanding-javascript-async-...

> Python is also compiled to a bytecode before interpretation; were still don't call it a compiled language.

https://softwareengineering.stackexchange.com/questions/2455...

> macros aren't significantly faster

> you can always precompile your abstraction on program load at the expense of slower start times

http://www.ilikebigbits.com/blog/2015/12/6/the-fastest-code-...

> How can I ask it what type a function returns or what types it's arguments should take?

http://www.braveclojure.com/core-functions-in-depth/