12 ms·
REPL vs CLI: IDE wars
- tomconnors 5y agoThis post could be summarized as "write as much of your project's tooling as you can in the project's main programming language and the project's main programming language should be Clojure" and I agree wholeheartedly.
- blacktriangle 5y agoI think their point is bigger than that. Historically one of the major points against using Clojure to write your tooling was the slow startup times which are just painful from the CLI. It looks like the clj-exec idea linked to in the article is the secret sauce that makes moving to writing your tooling in Clojure a workable idea, since now you have a unified calling convention for both calling tooling from the REPL during development but also activating your tooling from the CLI in some CI or build pipeline where the massive Clojure startup times don't matter. This article comes at a perfect time, we're just starting a new Clojure project and were looking into how to automate tooling since we'd been burned by Clojure startup times before. Looks like clj-exec means we can now unify our work on Clojure.
- Borkdude 5y agoAlso check out https://babashka.org https://babashka.org: it offers Clojure scripting with very fast startup time. It also has a task runner (similar to make, just, etc.) that can be used to store long invocations (like clj-exec tends to have).
- utxaa 5y agowhat about the horrible debugging experience?
- kinjba11 5y agoDoes GraalVM work with Clojure? I would assume that would solve the startup time issue for a project like a CLI tool.
- toomanybeersies 5y agoThe general gist of the article also applies to Ruby too.
- smw 5y agoI think you might be missing the main "aha!" of clojure REPLs vs REPLs in non-homoiconic languages: you can very easily execute small parts of the program you're editing without re-typing the code. In emacs, for instance, you often use `eval-last-sexp`, by default bound to C-x C-e. This lets you move your cursor to a particular point in the file, often deep in a function, and get the results of just the form(s) you're pointing at. This is a superpower! It lets you test small pieces of your code without writing any test harnesses or scaffolding. Try it with an editor that embeds the repl and has these commands, and you'll never want to develop any other way. It does cause you to want to structure your code in 'repl-friendly' ways, so that you can, say, restart your main server or reset your state without restarting the whole process.
- lennoff 5y agoWhen you evaluate a form deep in a function, how do you handle variables that are defined "outside"? How can you evaluate such expressions?
- synthc 5y agoCider (the clojure plugin for emacs) has a variant that prompts you for the the values for those variables.
- weavie 5y agoOh nice. That's one place where Clojure beats Common Lisp then..
- PuercoPop 5y agoDoes it? Asking for a value when encountering an unbound variable is a default restart $ sbcl This is SBCL 2.1.1.52.HEAD.321-f8a57bcca, an implementation of ANSI Common Lisp. More information about SBCL is available at <http://www.sbcl.org/>. SBCL is free software, provided as is, with absolutely no warranty. It is mostly in the public domain; some portions are provided under BSD-style licenses. See the CREDITS and COPYING files in the distribution for more information. \* (\* x x) debugger invoked on a UNBOUND-VARIABLE in thread #<THREAD "main thread" RUNNING {1001860103}>: The variable X is unbound. Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL. restarts (invokable by number or by possibly-abbreviated name): 0: [CONTINUE ] Retry using X. 1: [USE-VALUE ] Use specified value. 2: [STORE-VALUE] Set specified value and use it. 3: [ABORT ] Exit debugger, returning to top level. (SB-INT:SIMPLE-EVAL-IN-LEXENV X #<NULL-LEXENV>) 0] 2 Enter a form to be evaluated: 3 9 \*
- dgb23 5y agoSome Clojure frameworks and libs do provide REPL support for things that we would otherwise expect to be CLIs, such as running migrations, (re-)starting services and so on. The startup time criticism is valid, but in context of development you typically don’t restart it except you pull in deps (very rare) or something terrible happens (rare). Then, there is also borkdude/babashka which is a Clojure powered scripting tool with fast startup times due to GraalVM.
- geokon 5y agoThanks for introducing me to `add-lib` This is going to be a huge time saver :) More info here: https://insideclojure.org/2018/05/04/add-lib/ https://insideclojure.org/2018/05/04/add-lib/ Hopefully there will be some way to just "reload" your whole `deps.edn` though "If you get an error, the execution stops by default and you get a stack trace." I'd say the other missing piece of REPL development is that while you get a stack trace, you don't get a program state like you do with GDB or ELisp. Maybe I'm "holding it wrong" but this causes a lot of friction and lost time. I'd be curious how others approach this. And that all being said, the CLI doesn't relaly offer a better alternative here. For entirely replacing the CLI I think that since Clojure is a general purpose language there ends up being a tad more boiler plate than you'd like.. It's not at the point where you're gunna just run `clj`, load in some library with `add-lib` and start messing around b/c things are just a tad too clunky. For instance if you wanna read in a CSV file (I had to look this up) (-> "my-csv-file.csv" (io/file) (.getCanonicalPath) (io/reader) (csv/read-csv :separator \,) (#(into [] %))))) Uhh.. so you're prolly gunna want to wrap that up in a helper function. I personally end up making a dummy "project" where I keep a bunch of helper functions and then doing my REPL "scripting" and messing around in that. It feels a bit wrong.. but at least to me it looks like a solvable limitation. Given a nice set of helper libraries you could probably get to a point where a bare `clj` REPL would be as ergonomic as a more explicitely interactive language like R/MATLAB/etc.
- marcosdumay 5y ago> Maybe I'm "holding it wrong" but this causes a lot of friction and lost time. It is a completely non-problem for functional code. It is a big problem for imperative code. You can't just write all of your code in a functional style, but depending on what you are doing the limit gets larger or smaller. So it's normal that this will be a showstopper for some people, and irrelevant to others.
- geokon 5y agoI don't really follow.. How is it a non problem in functional code? For instance you have some recursive idempotent function that blows at some point. Wouldn't you wanna see the state at which things broke? Just because things are functional doesn't mean you always knows the inputs at all times
- lateusername 5y agoConsidering its ecosystem, what do most here use Clojure for or what do you think is its sweet spot? I used it in a project with pg/ring/reitit and while some things were nicer it was not better enough to make me switch from pg/nodejs/express for new projects. Used emacs with inf-clojure (found cider too bloated and buggy, was tempted to port some features of cider to a fork of inf-clojure) and macros were helpful in a couple of places, but other than that, you can write JS almost as how most use Clojure. Also found JS to be faster unless you got out of your way to write Javaish Clojure, which reminds that is was a time sink having to deal with Java libs(it's really not as "seamless" as most advertise) because of no Clojure equivalents.
- bcrosby95 5y agoNowadays I use Clojure as my primary general purpose programming language. As far as sweet spots go, due to pervasive immutability, I think it can be a good choice anytime you're dealing with concurrency. If you're dealing with relatively straightforward web applications, outside certain specialized scenarios (and certain really bad choices) I don't think language choice matters.
- blacktriangle 5y agoClojure's sweet spot is very much the "situated program" Hickey likes to talk about. Clojure is a great fit for line of business apps where maps really are the right choice for modeling your domain. We recently evaluated Clojure vs node for an upcoming project and went with Clojure because while most of the work really is just querying a db and writing JSON, we also do a fair amount of heavy reporting and pdf generation which will bring node to a crawl relatively speaking. Rather than have to take on the operational complexity of microservices for the slower parts of our app we just went with a Clojure monolith. The draw of a single language to work with for both client and server was very tempting, but ultimately the JVM won out on the server. I'd be amiss if I didn't also say the whole Deno situation has us worried about the long-term implications of spinning up a brand new node project, where the JVM seems far more reliable logistically.
- ithrow 5y ago
- a-dub 5y agoprotip; you can bring this flow to any language with a repl using vim-slime and a terminal manager like tmux.
- revscat 5y agoThat was my impression. I’ve been doing this for years with Ruby, tmux, and some custom zsh widgets. https://github.com/jchilders/dotfiles https://github.com/jchilders/dotfiles
- godshatter 5y ago> Don’t forget the set -euo pipefail at the beginning of your script. What does -euo means? I don’t know, I copy-pasted it and man set said there is no manual entry for set. Use "man bash" to find the list of built-in commands. Scroll way down, or search for "SHELL BUILTIN COMMANDS". The "e" command-line switch to the set command tells the script to exit immediately if there is a non-zero return value. The "u" switch tells the shell to treat unset variables as errors when performing parameter expansion. The "o" switch enables the following option (in this case "pipefail"). "pipefail" tells the script to return the value of the rightmost command that returned with a non-zero value in a pipeline. This is all paraphrased, the details are in the man page.
- ajkjk 5y agoIt's kinda nonsense that `man set` doesn't provide that information. But it's kinda nonsense that those flags aren't the default, anyway.
- aidenn0 5y ago"man set" can't provide that information trivially without knowing your shell; dash/bash/ksh/zsh will all be subtly different. However, if you are running bash, "help set" is fine. FWIW, I really don't like pipefail as a default (e.g. piping to "head" causes random pipefails) and -e also has some confusing semantics. Also either failglob or nullglob are more important than either and -C is useful for some scripts as well. [edit] Simple example of confusing "set -e" semantics: The following does not print "hi" and shows returns error status, as expected: (set -e; echo hi); echo $? But put it in an if statement, and it's suddenly success, and does print hi: if (set -e; false; echo hi); then echo hello; fi The same problem applies to functions. The below function will remove all files in the current directory if it's called from a conditional, but not otherwise! foo() { set -e cd /some_directory # set -e means we exit if this fails rm -rf * } I think just defining a die() function and using it after any command that must succeed is more verbose, but less error prone: cd /some_directory || die "chdir failed" rm -rf *
- 5y ago
- deleted 5y ago[deleted]
- airocker 5y agoREPL will not work well with multitheading/futures in Clojure.
- utxaa 5y agoor the ridiculous namespace system.
- jefurii 5y agoLinked to in the article: UNIX as IDE (https://blog.sanctum.geek.nz/series/unix-as-ide/ https://blog.sanctum.geek.nz/series/unix-as-ide/)
- dig1 5y ago> I use add-lib branch of tools.deps.alpha that allows me to add dependencies dynamically at the REPL and then start using them immediately, just like in the shell. You could do this with (battle-tested) pomegranate [1] ages ago, which is used by leiningen as the default resolver. [1] https://github.com/clj-commons/pomegranate https://github.com/clj-commons/pomegranate
- mgiannelis 5y agoI want to publish thois theory on tech business news https://www.techbusinessnews.com.au https://www.techbusinessnews.com.au How would I go about adding this to a feed?