For some feedback, you might consider using Let's Encrypt for setting up free TLS certs, or hosting on Github Pages to get it automatically. With just HTTP, my browser (and many others) will show a scary warning telling me not to use the site.
https://tryclojure.org/ is working correctly. I'm figuring out why Netlify is not redirecting automatically.
I put each of these implementations in a load test lab where I collect then analyze the performance data for comparison purposes. Those two blogs include the performance results. It is hard for Clojure to compete with other tech stacks, primarily because each method call in Clojure goes through Java reflection which is pretty slow. There is a way to prevent the use of Java reflection in Clojure but then the Clojure code doesn't end up looking very Lisp like. The usual advise is to use type hints very sparingly like in hot spots in the code.
I still like Clojure, though.
That's not true. Clojure functions calling each other do not use reflection. It is only when interoping with Java, and trying to call a Java member method of an object that reflection might be used if there are multiple concrete implementation of the method and the compiler can't find the right one at compile time. In which case, you can provide a type hint to tell it which one you want.
https://cuddly-octo-palm-tree.com/posts/2022-02-20-opt-clj-6...
A good resource if you really want to push the perf limit http://clojure-goes-fast.com
While it is true that not every method calls goes through Java reflection, it is also safe to say that every API call to this kind of service will end up making a lot of calls via Java reflection unless you use types hints all over the place in the DAOs.
One piece of feedback - "Range of N" should clarify that "range" is generating N numbers starting at 0, so N won't actually be included ("0 to N exclusive"). I know this is fairly common for most programming languages, but "So (range 5) will return numbers from 0 to 5." is still ambiguous enough that I think it should be clarified.
Also can't copy the text from the instructions or REPL (at least in Vivaldi on Windows 10).
edit: I'm not a huge fan of the "multiples of 11" algorithm ("100" and "11" never actually show up in the expression...), so I ended up with this abomination which I think strongly reiterates that I don't get Clojure yet...:
> (let [multiplier 11 limit 100] (map (fn [n] (if (> limit (* n multiplier)) (* n multiplier))) (range 1 (/ limit multiplier))))
(11 22 33 44 55 66 77 88 99)
The conditional is probably not needed, but I was curious how they worked... Also glad that range accepts a float for the second value, as I forgot to wrap that in "int" (and "Math/round" didn't seem to work). And yeah, tested and this works just as well for at least the basic version:
> (let [multiplier 11 limit 100] (map (fn [n] (* n multiplier)) (range 1 (/ limit multiplier))))
Types... I find most people missing static type checking are really reliant on a certain way of programming which isn't applicable to Clojure, e.g. write code, look for red squiggly lines, fix type signatures, compile, wait, fix the bugs the compiler tells you about.
Clojure is much more exploratory in the way that you are always connected to a live system (like other Lisps) plus most of your functions are small and pure, making their logic self-contained and simple to deal with in isolation.
I use a repl in Haskell just as much as I used a repl in Clojure.
The development experience is also somewhat similar if we're talking in terms of what you described here. In Clojure, you also fix the bugs the compiler tells you about, except the compiler in this case is one you partially implement yourself with a test suite.
> plus most of your functions are small and pure, making their logic self-contained and simple to deal with in isolation.
This is hardly exclusive to Clojure.
I've built my SaaS in Clojure and ClojureScript and really enjoy working with it every day since 2019!
Would it be possible to add `add-libs` from `tools.deps.alpha`? https://insideclojure.org/2018/05/04/add-lib/ Then you could add libraries at runtime directly from the REPL
You could then pair it with a pastbin and share/run code snippets (with some URL shortener). Could you imagine the possibilities for bug reports for instance? :D You could reproducibly demo bugs (well and features as well)
SCI can be combined with Reagent, etc for online tutorials as well, or to build web apps, like you can see here: https://babashka.org/scittle/
I get there are a few ways to have a REPL in the browser, but it seems a bit primitive if you can't get any libraries in (short of copy pasting namespaces). I understand babashka includes some internally - so it makes it at least suitable for a subset of tasks. But I think this dynamic library loader is a more flexible solution (or I'm overlooking some technical hurdle here)
Jokes (?) aside, great job! I love that the input is an actual `input`, and not a `contenteditable`, big win for accessibility!
ALmost all editors I've seen allows you to send code to the REPL from your editor, and get the results in line in your editor.
Typing into the REPL would be bad practice.
=>(map inc)
function(b){return function(){function c(k,t){t=a.g?a.g(t):a.call(null,t);return b.h?b.h(k,t):b.call(null,k,t)}function d(k){return b.g?b.g(k):b.call(null,k)}function e(){return b.s?b.s():b.call(null)}var f=null,g=function(){function k(w,u,B){var p=null;if(2<arguments.length){p=0;for(var I=Array(arguments.length-2);p<I.length;)I[p]=arguments[p+2],++p;p=new gc(I,0,null)}return t.call(this,w,u,p)}function t(w,u,B){u=hc.i(a,u,B);return b.h?b.h(w,u):b.call(null,w,u)}k.o=2;k.u=
function(w){var u=r(w);w=v(w);var B=r(w);w=xe(w);return t(u,B,w)};k.j=t;return k}();f=function(k,t,w){switch(arguments.length){case 0:return e.call(this);case 1:return d.call(this,k);case 2:return c.call(this,k,t);default:var u=null;if(2<arguments.length){u=0;for(var B=Array(arguments.length-2);u<B.length;)B[u]=arguments[u+2],++u;u=new gc(B,0,null)}return g.j(k,t,u)}throw Error(\"Invalid arity: \"+arguments.length);};f.o=2;f.u=g.u;f.s=e;f.g=d;f.h=c;f.j=g.j;return f}()}
The output is correct as far as I can tell but that's some pretty hairy Javascript right there.Edit: Formatted code.
And auto-complete when it comes to Java library interop?
I tried Clojure, to build a tool on top of a popular Java library that I was new to.
And compared to other JVM languages it was not good because there was no type-checking or intellisense/autocomplete popups for the methods.
The short answer is that you don't. Rich Hickey, the creator of clojure, made the language *very* opinionated by design. And one of the strong options is that folks should be aiming for simplicity in their code, checking their code as they go along using the repl in real time. Dynamic typing makes it much easier to do this.
Whether or not this is a good approach (I happen to like it) is up for debate (and likely a matter of taste too), however that's at least some of the rationale at a high-level.
To really enjoy using clojure, you have to do things the way the language wants you to. If you try to bring your java style workflow into clojure, it's not going to be a lot of fun.
I think the other key to working with clojure is to set up your program as a set of transforms on simple data structures as much as possible such that complex class interactions aren't used much, and that cuts down on the need for great intellisense and static typing. Ymmv, of course, but I've found this way of working more productive than when I was working in Java.
The lack of type checking is due to the entirely different paradigm that Clojure is built around. There are many benefits to it, but I can understand the frustration if you expect to use the language in a way that goes against that, especially when dealing with the interop layers. The fact that it is built upon the JVM is powerful but it's not necessarily an easy language switching from a language like Java.
If you've worked in Python, Ruby or JS it's pretty similar.
What I do is I use variable names that make it more obvious what type things are. I also make use of destructuring and Clojure Spec to indicate what keys a map has or what values in a tuple are supposed to be.
Also the REPL can help you quickly explore the state and functions which you can use to try and inspect the types, that's useful when trying to understand someone else's code base which might not have had the most readable code.
And since you do REPL driven development, as I code I run the code constantly in the REPL which will throw type errors when I make one. It has the bonus of catching logical errors as well as helps me figure out how to implement what I want more quickly.
> And auto-complete when it comes to Java library interop
You should get some auto-complete here depending on your tooling. It should lost all possible methods of an object, just not constrained to the direct type. For me that's often enough as I know kinda what I'm looking for, so I can find it and auto-complete.
If you want the auto-complete to a specific type, you can type hint the object and then the auto-complete will list only methods of that type.
Finally, I do rely on the Javadoc a lot.
Runtime asserts, typically.
It ends up being somewhat less of an issue in practice than someone coming from Java or Kotlin would assume, I think, because clojure really only has a single datatype: a sprawling, immutable soup of nested maps and vectors. The design of the standard library is such that your standard data manipulation functions will basically always work on every data structure you get passed, so you end up designing your internal APIs so that they take in a blob of data, perform an operation if the soup has the right components (and probably throw an exception if not), and then spit out that changed blob of data.
Is this better (or at minimum no worse) than having a type system? That's a broader question of philosophy and taste that I'm still undecided myself (for instance, I think the type systems in Haskell and Rust are really quite valuable, but the ones in Java and Go don't really pull their weight), but it's not something I really miss on a day-to-day basis writing clojure code--it's just a different way of doing things.
> And auto-complete when it comes to Java library interop?
My experience as both a professional and hobbyist clojure user is that this is not a huge issue in practice:
- Java interop tends to get wrapped in clojure defns so you can get your auto-complete pop-up off the namespace alias.
- At least with my setup (emacs+cider), adding a (:import (...)) clause to a ns form allows me to autocomplete on all method names of classes in scope (and provides type-annotated signatures), so I can still auto-complete when performing interop. I believe IntelliJ+cursive is even better about that sort of thing, and if I were working directly with Java libraries/APIs a lot I would probably consider switching IDEs for that reason.
First of all, yes there is. Maybe your editor didn't support it, but e.g. IntelliJ IDEA with the Cursive plugin does.
See here, I have autocomplete and intellisense using VSCode and Calva, but I can also hover a function and see the clojure docs for it, then copy them to my IDE, and run the example code all within my editor. Then remove it when I'm done playing. The doc examples go into a rich comment form.
Also, you can have autocomplete, it's orthogonal to static typing.
1. https://yogthos.net/ClojureDistilled.html 2. http://clojurekoans.com/
Java the JVM? You _probably_ don’t need to know it to get started, but you’ll have to get familiar with JVM deployment, dependency management, packaging, JVM memory settings, etc if you want to run something outside your dev machine. Getting to 90% here is pretty easy, but the last 10% (to me at least) tends to feel a lot more difficult that other languages.
As a small pro-tip: use jstack. I wish every language came with a jstack. It just dumps the stack traces of all threads of a program by pid.
It doesn't require Java as such - I can't really program in Java and Clojure is great. A lack of Java fundamentals is still a handicap and probably around my #3 annoyance as a Clojure user. It makes dependency management (#2 annoyance) harder because the already complicated Java dependency management systems gets a thick layer of Clojure confusion ladled on top. The stacktraces obviously take the spot for #1 annoyance, which is a consequence of the Java but knowing Java won't help navigate them.
Can't recommend Clojure enough though. Very fun language.
The tutorial showcased here runs on ClojureScript for example.
So:
(reverse "abc")
Becomes:
("c" "b" "a")
In ClojureScript. But in Clojure it becomes:
(\c \b \a)
Since Java has a character type but JavaScript doesn't.
It's little things like this that. The benefit here is superb interop, but in turn you aren't as isolated from the host language as you would be in another language.
Also, please use "Show HN" for own submissions like this project (see https://news.ycombinator.com/showhn.html for details).
Forgot Show HN. Now I cannot edit the title anymore