> 1. Economy of expression Funny, this list is the same one I use as to why I'm so annoyed with Clojure right now. I inherited a mission-critical Clojure ML library my team uses for it's primary business goals. It was written 4 years ago by a research scientist- who quit 3 years ago. We know what it's supposed to do. We know that it seems to do the job well. We just can't understand the code well enough to be certain…
Didn't DJB famously use very small variable names when writing qmail in C? This is (obviously) not unique to C or Clojure. But it is something that is occasionally influenced the language best practices or culture, as you see in the giant names everywhere in Objective-C and the opposite in the push for minimalism in Ruby. The names of complex functions, even with documentation, tends to be a bigger problem with scale…
Why Clojure?
121–130 of 202 posts
Re: Why Clojure?
#122Earlier quoted context omitted.
The above posters point on Homoiconicity is a big one, here is a quick (and silly) example for anyone unfamiliar with the term. Take the following clojure: (+ 1 2) => 3 Here the ( ) delimits, a list, and as the blog post says most lists are function calls. In this case the function is + and it's params are 1 and 2. It means if we do this (1 + 2) We get an exception that 1 isn't a function... However, we can tell Cloj…
Why not just do that as a function though? Why use a macro?
The main point of macros is when you use a regular function in Clojure you have applicative order evaluation. Macros do not, as macros are designed to transform and generate code.
A better example would have been the (when) macro.
In Clojure you have (cond) and (if) for conditional evaluations. If has the form
(if (cond)
(when-true)
(when-false))
e.g. (if (= 1 1)
(println "1 = 1")
(println "uh-oh the unviverse is broken!"))
=> "1 = 1"
Now what if we want to do more than 1 statement on the true path and we dont care about the false path? (if (= 1 1)
(println "1 = 1")
(println "also hello"))
This won't work as now "also hello" is the false path.. We can use (do) to specify multiple things to be done. (if (= 1 1)
(do
(println "1=1")
(println "Also hello")))
1=1
Also hello
But writing (do) is a bit of a pain, so an alternatie would be (when) (when (= 1 1)
(println "1=1")
(println "Also hello"))
Will print, when run, "1=1"
"Also hello"
COuld we implement this as a function, sure? BUt we would have to quote the function calls when we pass them so they aren't evaluated and then eval them if true. You could do it, but it would be messy.Essentially, we want to write
(when (= 1 1)
(println "1=1")
(println "Also hello"))
But want the code that gets compiled to be: (if (= 1 1)
(do
(println "1=1")
(println "Also hello")))
So we want to extend the language so we can write (when) and the compiler will write us an (if (do))This is incredibly easy in Clojure. We just need to create a list where the first item in the list is `if`, the second item is the conditional we pass to the macro, the 3rd item is a sublist, of which its first item is `do` followed by the list / functions to execute when our test evaluates to true!
(defmacro when [test & body]
(list 'if test (cons 'do body)))
This is just the source code for the actual clojure core when macro. But you can see how easy this is!If we run the macro expansion on when we can see the code that it generates:
(macroexpand '(when (= 1 1)
(println "1=1")
(println "Also hello")))
=> (if (= 1 1) (do (println "1=1") (println "Also hello")))
Another example would be the reader macro:Say we have the nested function calls:
(reduce + (filter even? (range 1 11)))
=> 30
After you have a lot of nesting this can get difficult to read, so you have a macro ->> which is the threading macro. This takes a series of functions and threads the result of each function as the input to the next. (->> (range 1 11)
(filter even?)
(reduce +))
The source code for this is almost as simple as (when) (defmacro ->>
[x & forms]
(loop [x x, forms forms]
(if forms
(let [form (first forms)
threaded (if (seq? form)
(with-meta `(~(first form) ~@(next form) ~x) (meta form))
(list form x))]
(recur threaded (next forms)))
x)))
The ` ~ and ~@ are just doing some quoting and quote splicing to determine when we want stuff evaluated.Basically, using macros you do things like control symbolic resolution time, extend the compiler to create a DSL spcific to your domain and reduce boiler plate code.
That's before you start getting into properly weird stuff like anaphoric macros.
Re: Why Clojure?
#123The response to "but is it slow" is pretty disappointingly bad. > No. Clojure is not slow. Oh, look, it’s not C. It’s not assembler. If nanoseconds are your concern than you probably don’t want Clojure in your innermost loops. You also probably don’t want Java, or C#. But 99.9% of the software we write nowadays has no need of nanosecond performance. I’ve built a real time, GUI based, animated space war game using Clo…
Yes, Lisp makes this a non issue for the most part, because the compile and test iteration loop is instantaneous and integrated fully within your IDE/Editor
Still, you get pretty decent auto-complete and static linting warns on quite a few things. For that though you'll want to use IntelliJ with Cursive or Emacs with Cider and clj-kondo + joker flywheel linters.
Edit: Let me address the performance part of your comment as well. I think using a game with 20fps as an example was to show that it could even achieve such performance. Languages like Java, C#, Clojure, Python, Ruby are normally bad choices for games as they are not performant enough. So most games are implemented in C++ with an embedded scripting language on top. So the fact a pure Clojure game can hit 20fps with lots of on screen object is actually pretty good in this case.
In general, for idiomatic Clojure code, you should expect to be within 10% the performance of pure Java. Non idiomatic Clojure can often match Java's performance, and when it can not, you can implement the hot paths in Java and use interop very easily.
For ClojureScript, performance is pretty on par with JavaScript.
Startup times are the biggest issue, if pure Java takes 80ms, Clojure will take more around 500ms to start. The issue is that each Clojure function is a Java class needing to be loaded at startup, and the JVM is very slow at loading classes. GraalVM can be used to make native images, and those will start in around 10ms, but your code might need to be adjusted a little as the native images don't yet support all runtime features.
ClojureScript startup times are pretty on par with pure JS running on Node.
Re: Why Clojure?
#124Earlier quoted context omitted.
You couldn't write it as a function, unless you pass in the 1+2 part to an outer function (macro) as either a list of arguments (1, math.add, 2) or a string (+you have an eval fn). At that point you're emulating lisp without the elegance, and the first approach is only possible because functions are first class objects. If you were trying to rearrange an expression that had control flow or keywords in it (eg. Modify…
I'm apparently too stupid for you :). Please excuse my ignorance. Why can't you do something like the below? (defun swap (x y) (y x)) Then you can call it like: (swap (2 3)) => (3 2)
You could do `(defn swap [the-list] ((second the-list) (first the-list)))` (which would invoke the second item as a function on the first item). It comes down to is the list a literal list as a parameter to something or is the first item a function to be invoked.
Re: Why Clojure?
#125> 1. Economy of expression Funny, this list is the same one I use as to why I'm so annoyed with Clojure right now. I inherited a mission-critical Clojure ML library my team uses for it's primary business goals. It was written 4 years ago by a research scientist- who quit 3 years ago. We know what it's supposed to do. We know that it seems to do the job well. We just can't understand the code well enough to be certain…
This is why I like python and C, almost impossible to layer in multiple layers of opaqueness with them. You can almost always deconstruct what the original person was trying to do, whether they were successful or not.
Re: Why Clojure?
#126Earlier quoted context omitted.
Why not just do that as a function though? Why use a macro?
It was an admittedly simple example that was to show how easy it is in Clojure to treat code as data and data as code. The main point of macros is when you use a regular function in Clojure you have applicative order evaluation. Macros do not, as macros are designed to transform and generate code. A better example would have been the (when) macro. In Clojure you have (cond) and (if) for conditional evaluations. If ha…
I've read a few Lisp books and dozens of internet blog posts, so I know about macros and why people use them without getting the full understanding which comes with actually writing code.
Re: Why Clojure?
#127Earlier quoted context omitted.
You couldn't write it as a function, unless you pass in the 1+2 part to an outer function (macro) as either a list of arguments (1, math.add, 2) or a string (+you have an eval fn). At that point you're emulating lisp without the elegance, and the first approach is only possible because functions are first class objects. If you were trying to rearrange an expression that had control flow or keywords in it (eg. Modify…
I'm apparently too stupid for you :). Please excuse my ignorance. Why can't you do something like the below? (defun swap (x y) (y x)) Then you can call it like: (swap (2 3)) => (3 2)
(defn swap [x]
(list (second x) (first x) (last x)))
(swap (1 + 2))
=>Exception! 1 isn't a function.
Clojure tried to evaluate it's argument to swap, and the argument was (1 + 2), which is a function call, where the function is 1 and the arguments are + and 2.So we quoted it in the function call by putting ' in front of the list '(1 + 2):
(swap '(1 + 2))
=> (+ 1 2)
Here, we stll didn't get 3 as our output... We got (+ 1 2), which is a list. Because the function returned a list, it didn't return code! It might look like code, but it's not code! It's a list.So if I was to
(+ (swap '(1 + 2)) (swap '(3 + 4)))
=> Crashes! Can't convert alist to a number.
Because what it actually runs is (+ '(+ 1 2) '(+ 3 4))
Whereas with the macro (+ (swap (1 + 2)) (swap (3 + 4)))
=> 10
Works because the macro gets expanded BEFORE compile time, and our swap code gets replaced out with the code the macro generates! (swap (1 + 2))
actually compiles as: (+ 1 2)
SO at runtime, that will be 3.Re: Why Clojure?
#128> 1. Economy of expression Funny, this list is the same one I use as to why I'm so annoyed with Clojure right now. I inherited a mission-critical Clojure ML library my team uses for it's primary business goals. It was written 4 years ago by a research scientist- who quit 3 years ago. We know what it's supposed to do. We know that it seems to do the job well. We just can't understand the code well enough to be certain…
This is true literally for every language out there. My team maintains a Java application from over a decade ago. It's an utterly incomprehensible mess that nobody understands, and it's incredibly difficult to make any changes to the project. It's not the job of the language, but rather that of the developer to write clear and readable code. This problem is addressed by using good design practices, code reviews, test…
But at least the Java is overly verbose. It takes a while to read all that code. With the clojure stuff, I'm staring at the same line for just as long and getting no where.
Re: Why Clojure?
#129Earlier quoted context omitted.
Not the grandparent, but I realized the other day that I'm at nine years of clojure, so... What's made clojure so great, imo, is its unicorn status as a principled-yet-practical language. That "principled" part is not worthless---it means that a lot of great minds are drawn to it. Before react took over the world, clojure folks were already taking steps in that direction. A lot of other things. The "sequence" as a co…
What is GUI programming like in Clojure? What libraries exist, and what paradigms are used? E.g. is it more like React or is it more like Gtk/Qt?
There are downsides, however. The UIs that it creates definitely look like Swing apps. I.e. ugly at least on Linux. Also, the up to date documentation is difficult to find, I'll like to it here when I get home. The GitHub-linked docs work fine, but they do miss a few features.
Re: Why Clojure?
#130> 1. Economy of expression Funny, this list is the same one I use as to why I'm so annoyed with Clojure right now. I inherited a mission-critical Clojure ML library my team uses for it's primary business goals. It was written 4 years ago by a research scientist- who quit 3 years ago. We know what it's supposed to do. We know that it seems to do the job well. We just can't understand the code well enough to be certain…
> ML library (...) It was written 4 years ago by a research scientist (...) Do you believe the language is the issue, been written by someone not trained on software engineering practices? Would you find untested (I'm betting this code you inherited lacks tests, from experience working w/ ML research code artifacts) verbose Algol-family code any easier to understand?
But we own lots of legacy stuff. It's only this one that gives me a headache.
Honestly, I wish it had bugs or failed occasionally so I could justify replacing it!