10 ms·
I 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
by smw 5y ago
I 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 \*
- fiddlerwoaroof 5y agoYeah, in fact, Clojure needs special tooling for this, while it’s built into CL’s execution model.
- marciol 5y agoThis is interesting tool[1] that allows much the same with Clojure [1]: https://github.com/IGJoshua/farolero https://github.com/IGJoshua/farolero
- weavie 5y agoActually, yes I seem to remember it doing that a while ago. I think my Emacs setup has messed up somehow. Now I just get: Debugger entered--Lisp error: (void-variable k) And then a stack trace. No option for restarts. It works if I run from the command line though..
- eckesicle 5y agoWhich function is that? I couldn't find it in the docs
- utxaa 5y agohmmm ... what if it's a closure, and it depends on a huge lexical environment?
- weavie 5y agoYou can essentially give values to global variables with same name as those outside variables.
- Graziano_M 5y agoThe repl maintains an environment.
- dgb23 5y agoYou can use a REPL powered debugger. You can interact with the debugger to a higher degree than with most mainstream languages. It’s not quite as powerful as CL though, from what I’ve read. Or you pull out expressions and test them in isolation with given (assumed) inputs.
- Scarbutt 5y agoBy evaluating outer forms first.
- slifin 5y agoCursive has a repl powered debugger just put your breakpoint there use the repl to trigger the call then use intellji's debugger to step through If you want to run expressions in context just use the expression editor like a normal repl Something I haven't tried yet is redefining functions at debug time can't see why it wouldn't work though
- notriskfree 5y agoSmalltalk famously let you code inside the debugger. You can run code with methods that don't exist yet and add them in the debugger when it notices they are missing.
- notriskfree 5y agoI suspect you are right. Rebinding an inner function inside another function that contains variables probably will not work. But you could still alter and rebind the entire thing. And that is still flexible; you can still change a running program on the fly with only that ability. And you can always reference objects at the top level.
- dragonwriter 5y agoHomoiconicity isn't required for that, all you need is to be able to map between source and AST enough to identify expression boundaries. Plenty of development environments for non-homoiconic languages provide the ability to evaluate a selected expression (not just the last one) in a linked REPL on demand. I love the Lisp family, but I don't know why its advocates often sound like they haven't seen a dev environment for a non-Lisp language since the early 1980s when pointing to “unique” advantages of Lisps.
- marcosdumay 5y agoHomoiconicity is an ill-defined concept. If you take every subjective feature from it, all it implies is that you are able to cut your code into well defined AST structures (what all languages have, but for some it's easier than for others). So, yeah, anything people do with Lisp could be done for any other language, it only requires more complex tooling. How much more complexity depending on the language, and it varies from "barely perceptibly more" into "that's completely not practical".
- js2 5y agoIt's a well-defined concept in theory but in practice it is not: https://www.expressionsofchange.org/dont-say-homoiconic/ https://www.expressionsofchange.org/dont-say-homoiconic/ https://news.ycombinator.com/item?id=20657798 https://news.ycombinator.com/item?id=20657798
- thanato0s 5y agoTo me, "only" and "complex" shouldn't be used in the same sentence. Big part of the job of a developer is to reduce complexity, and we barely do it (depending of the system). What is "barely perceptible" now can be "completely not practical" 2 years down the road.
- leephillips 5y agoIndeed; I do this every day with Vim and its term command, with Julia, Python, bash, Elixir—it works with any language with a REPL of any kind.
- tomrod 5y agoThis is the draw for Jupyter Notebooks as well. Execute code blocks. I remember when I learned this in Matlab. REPL are super nice for this! I've had a hard time in Java, Fortran, etc. in determining the right development patterns that are used in this space to be productive.
- bookofsand 5y agoStep1: Write a test. Step2, IDE: Right click + Run. Step2, CLI: $ run <test name> Bonus: The test is now part of your automated test suite, and will be ran many times to ensure things don't break.
- pjmlp 5y agoNow try to fix the code from the unit test when it breaks and redo the unit test, from the point where it broke.
- mypalmike 5y agoThrow a breakpoint in the unit test.
- pjmlp 5y agoIt won't work on the CLI version, and unless you are using an IDE with a backtracking debugging it won't help.
- lmm 5y agoSo that you can run your unit test from some unknown state that may not even be reachable from the current text of the code, rather than running it from the start in a reproducible way? Why would that be something you want?
- pjmlp 5y agoUnit testing was born in the Smalltalk community, good tooling is orthogonal to having tests.
- winrid 5y agoYou can do this in IDEA's IDEs too, just place a breakpoint and then you can "evaluate expression" in that context to call functions, read values, etc.
- fnord77 5y agoI remember doing this with emacs and C in the 90s
- winrid 5y agoNice. I definitely want to give emacs a solid try at some point.
- utxaa 5y agoi've used emacs for 20 years. i still spend 80% of my day in it ... and vscode is vastly superior for development.
- winrid 5y agoThe thing that bugs me about the IDEs is how slow they are. I'm super productive in them, but I really don't feel like they need to be so slow. I'm not even talking about the indexing which I get. In fact they weren't always unresponsive, seems Jetbrain's products have regressed in the last year, but mostly Webstorm. I use WS on OSX and Win10, and on both I get all kinds of slowdowns - different projects too. Tried disabling features, plugins, different GC, increased heaps. I may have to fire up J. Mission control at this point. IDEA and Clion on Win10 are mostly fine. It would just be cool to have all of these features, but the frontend be a responsive CLI.
- utxaa 5y agotry vscode ;) ~ 5*10^6 java code base, i don't notice any slowness. it would be impossible to do this in emacs.
- 5y ago
- simiones 5y agoThe homoiconicity of Lisp is not what gives it this superpower. It is rather Lisp's dynamic nature that actually allows you to keep running your project while also developing it that is crucial - something that Clojure running on the JVM can actually only approximate. For example, in a full CL, you can modify a class that is currently being used, and objects of that class will adjust accordingly (for most modifications). This is not possible for the JVM - to modify a class, you would have to unload it, which can only happen if the Class object is garbage collected, which requires all instances of the class to be GCd, and the ClassLoader that loaded that class as well - only then could you add a field with a default value to the class.
- utxaa 5y agoi get the magical aspect of repls in scheme and CL. not in clojure. not sure why. so i don't like clojure, but i feel like i should ?? why is that?
- tsimionescu 5y agoAs I said, I think Clojure is significantly iimpacted in how much dynamic reconfiguration it can handle by compiling down to the JVM. Still, personally I have little to no experience with Clojure, so I can't really comment too deeply.
- utxaa 5y agoi don't think that's it. i like scheme, and clojure is not scheme. it may just be that.
- fnord77 5y agoI can do this in Java and C with the right IDE.
- utxaa 5y agoi don't love java but no one can argue with the fact that it has fantastic tooling. properly written java is a joy.
- utxaa 5y ago> 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. but that depends on how the environment is looking at the time. during development, in scheme, i just have a function (reset) that reloads all the files of an app. sometimes i create a thread and poll the filesystem and reload everything that changed in the last second. that way i never have to C-x anything. if i need to eval a subexp i just jump on the repl and do it.
- nerdponx 5y agoPython can hot-reload an application from scratch, too. The idea here is to not reload everything from scratch, but to redefine functions incrementally/iteratively as you work.
- utxaa 5y agoif i change anything in filters.scm then i (load "filters.scm") instead of just sending a snippet to the interpreter to eval. i have this happen when i save the file. same thing, except i never have to worry about sending code or a mis-match between editor and repl. it also forces you to be more organized with code so that you don't override global state. i just code, save, code, save. you can have hooks run things after every save etc ...
- nixpulvis 5y agoWell, a UNIX REPL (read sh, bash, fish, etc) is pretty much "homoiconic", since everything is just a text stream. Of course the value of ARGV[0] will be some executable, written and possibly compiled, but that's no different than any other language which needs to eventually make it to hardware instructions.