Hacker News new | past | comments | ask | show | jobs | submit login
Notes on debugging Clojure code (thegreenplace.net)
97 points by melqdusy on May 30, 2017 | hide | past | favorite | 49 comments



A few more tricks:

- CIDER, the de-facto Clojure environment for Emacs, has a debugger[1]. This is also true for some of the larger IDEs, like Cursive.

- Timbre[2] has a number of cool debugging macros, especially spy, which you can tack onto any expression and it'll log it for you. Very useful, similar to the macro in the post, except you don't have to write it :)

I really can't overestimate how valuable a real REPL-driven development environment is.

[1]: https://github.com/clojure-emacs/cider/blob/master/doc/debug... [2]: https://github.com/ptaoussanis/timbre


I find debugging + error messages to be the worst part of Clojure.

In CIDER I have to mark my functions as debuggable before I can step into them - this is an odd requirement and a PITA when the functions are spread out through the codebase. It's so easy to accidentally commit a non-debuggable version as well.

Cursive is a lot better and more modern, with one painful problem: no proper support for 'break on exception'. So on exception the program just closes and I'm just left with a stacktrace in the console. You can turn on 'Java Exceptions', but this has multiple problems:

1. On load of Clojure files there is a ton of Java exceptions thrown, literally making it unusable

2. and if you disable those exceptions in the Condition field the boot up process takes forever to actually run (likely because it's interpreting the Condition field code over and over and over again..).

Any good solutions to these?


>In CIDER I have to mark my functions as debuggable before I can step into them - this is an odd requirement and a PITA when the functions are spread out through the codebase. It's so easy to accidentally commit a non-debuggable version as well.

When I'm connected to my repl, I often want to debug things I didn't mark as debuggable (hint: nothing is ever debuggable by default). I can jump to definition, toggle debugging, and then run some code (usually from a comment block or my scratch/user.clj (gets excluded by gitignore)). When I'm done, I can toggle it off or simply re-eval the function. In spacemacs, the normal mode binding for this is

    , d b


This!

Many times when people complain about Clojure, the solution is "just use already available tool X", but they say "no, I am not used to tool X" and then go to invent some half-baked solution in vanilla REPL. That is great for learning, but spreads lousy (mis)information about Clojure's development process. Folks, learn Emacs + CIDER, or at least Idea + Cursive...


I'm relatively new to Clojure development, but I definitely don't think this is the right attitude if we want Clojure to thrive.

Developers are very opinionated about their choice of tools.

I think our answer to critics on Clojure's debugging workflows who might be using tools that don't have first-class Clojure support shouldn't be to try to convince them to ditch their existing tools and force them to use the few tools that already have first-class support for Clojure debugging. This approach of tying the debugging experience to an editor/IDE only works when your community almost exclusively uses a few de-facto IDEs like in the Java world with Eclipse/IntelliJ and in .NET with Visual Studio. This isn't really the case with Clojure's target audience, who come from all kinds of backgrounds and prefer all kinds of different tools.

Instead, I think our answer should be to try to minimize friction for new developers by allowing them to use whatever tools they prefer and still have access to a first-class Clojure debugging experience, by decoupling debugging from editors and building a standalone debugging tool that can be run without any explicit editor support, but can of course be enhanced by editor integrations (and editors can of course still choose to maintain their own debugging mechanism). This approach would be analogous to the approach taken by the JavaScript community with various browser-based debuggers, and is why we don't hear people complain about the state of JavaScript debugging very often, even though very few editors have built-in JavaScript debugging support.


> works when your community almost exclusively uses a few de-facto IDEs like in the Java world with Eclipse/IntelliJ and in .NET with Visual Studio.

According to the latest official state of Clojure survey, large majority of people use only the few best tools: almost 50% use Cider, close to 30% Cursive, and more than 10% Vim + fireplace. Those are tools that have excellent support for Clojure, including fairly good debugging experience (not sure for Vim though). Those are the de-facto IDEs, with around 90% of the market.

I agree with you: newcomers should be able to start quickly and with as little friction as possible. However, I think that it is easier to choose a well-rounded tool that everyone uses and that is already configured to work with minimum friction and start from that until you build enough skill that you can do better than that, than to insist on the tool that was created for another job and will probably lead from one landmine to another.

I do not insist; if someone disagrees with this, they are in right to do what they think is best for them, but is it Clojure's fault if the path that they chose is not as nice as the paths that 90% Clojurists prefer, especially considering that all these tools are built by volunteers and essentially provided as a gift?


After being Clojure-only for a long time, I decided to write the front end to my last project in Elm, just to see what the fuzz was about.

While I miss code-is-data very much, there is no turning back from the type system and how there are no unpure functions. It is just so mind-blowingly easy to catch almost every bug I would usually write.

I'm afraid this is the point where I should try Haskell, and be unsatisfied for the rest of my professional life.


You should definitely try, even if you end up being somewhat unsatisfied every time you have to use something else.

When I write code in Haskell, it feels right, just like when I discovered Lisp. Only better. And oh, the purity! Not only in the mathematical sense, even the code is completely deprived of clutter.


But at what cost!? Every time I write Haskell code it reads like a mathematical proof that's beyond my ability to comprehend! Whereas my Ruby, Python, and Clojure programs read like a poem I might have written in third grade.


I don't think Haskell code looks like this. You don't have to use all the guru syntactic sugar; the library functions usually have pretty explicit names, and yours should have to.


I'm a ruby developer by day and, to me, OCaml-like languages like Elm and F# are better rubies. The language remains as concise and crisp as ruby, the algebraic data types help me more precisely formulate the logic for the app, and the static types remove a whole class of errors and makes refactoring much easier.

As an aside, I have a theory that Elm is a gateway drug to Haskell. Next stop, purescript?


> While I miss code-is-data very much

FWIW, Elixir has this (and is possibly the only non-homoiconic language to implement "true" macros)

Opt-in typing, though.

Regarding Elm, I love that runtime errors are considered compiler bugs.


Nim and Scala are not homoiconic, yet they both have true macros


Nim's macros look harder to use, with a fairly custom DSL in the AST (instead of, say, a simple datastructure like a nested map). I don't think you can pattern-match on AST fragments, either, like you can in Elixir.

Scala macros cannot change its syntax, Elixir macros can I believe (to some extent).


The AST of Nim is a simple tree, the only thing is that the nodes of this tree are tagged with a kind that represents the kind of syntactic element, since the language is not homoiconic


Dylan?


After reviewing Dylan's, it doesn't seem as powerful. And it's unclear how it handles runtime variables passed within code snippets to macros (Elixir has "unquote" for this)


Another trick is to define temporary inline vars. If I have some Ring handler, it can be quite annoying to construct a request by hand. Instead, I can do something like:

    (defn foo-handler [{{:strs [authorization]} :headers :as req}]
      (def *req req)
      (def *auth authorization)
      ...)
Then fire off a request in my browser and simply proceed to play around with `req` and `auth` in my editor/REPL, which is where I am running my local server from.


For people used to Scheme (or most other programming languages, I guess): note that def will create a var in the current namespace, not a locally-scoped variable. In Scheme, or e.g. in Python, a define or = in the same spot as that def would get you a local var. In Clojure, the idiomatic way to do the same thing is normally a let or something -- but using def here makes it easy to access from a REPL running in that namespace.

(A var is a Clojure thing that holds a value. If you've ever had like a "fully qualified path" to a class or object and had to describe it as a string or whatever, that concept is a first-class thing in Clojure, and called a var.)


> For people used to Scheme (or most other programming languages, I guess)

That's what LISP did since shortly after the last dinosaurs got killed by humans.


This is a terrible advice, and is not even necessary. Even println debugging is better than this. Not only defs are abused contrary to their intended use, they are difficult to distinguish from legitimate defs, and thus some may (and will!) slip away into the wild.


> Even println debugging is better than this.

Print debugging is great. A carefully placed print statement is something many great programmers swear by [1], despite the existence of "sophisticated" debuggers (see footnote in OP). This is a complement to this when you want to play around with the data directly.

> Not only def's are abused contrary to their intended use, they are difficult to distinguish from legitimate defs, and thus some may (and will!) slip away into the wild.

Never been an issue for me. It's just an extension and morphing of playing around with data in a standalone REPL. The temporary defs are never used in actual code logic, the prefix character makes it clear it is purely for exploration. Is it a dirty hack? Yes, but one might argue that's partly what Lisp is too: The most intelligent way to misuse a computer - Dijkstra (paraphrasing) Lisp

Some unsolicited feedback: you might want to consider that there is more than one way to skin a cat. If you want to understand the trade-offs of solutions people find, you might find it useful to not dismiss solutions outright. Especially something as universally useful like print debugging, considering how many people use it. Instead, a more useful question to ask yourself is: why do experienced programmers use print debugging when they are aware of the existence of debuggers? That might expand your worldview, as opposed to categorizing these people as "doing it wrong". See Worse is Better etc.

1: From this lovely quote by Brian Kernighan: The most effective debugging tool is still careful thought, coupled with judiciously placed print statements.


Why not define debug-req and debug-auth as atoms before this function, and set them with (reset! debug-req req) etc.?


This actually looks pretty handy. Not sure why you'd think it's so terrible - how else would you capture a request in a variable to play with it in the REPL?

I think a lot of the constraints can be relaxed during the development phase.


There are many ways one would do that in various Lisps during development.

For example a dynamic variable could be accessible in the debug repl:

    CL-USER 56 > (defvar *bar*)
    *BAR*

    CL-USER 57 > (defun foo (baz)
                   (let ((*bar* (* 1000 baz)))
                     (break)
                     *bar*))
    FOO

    CL-USER 58 > (foo 5)

    Break.
      1 (continue) Return from break.
      2 (abort) Return to level 0.
      3 Return to top loop level 0.

    Type :b for backtrace or :c <option number> to proceed.
    Type :bug-form "<subject>" for a bug report template or :? for other options.

    CL-USER 59 : 1 > *bar*
    5000
The value of a lexical variable can be retrieved.

    CL-USER 60 : 1 > :l baz
    Value of BAZ is:
    5

    CL-USER 61 : 1 > *
    5
In something like McCLIM or SLIME I would print the object to a listener and the window system remembers the value.

In some other Lisp I would call (inspect foo) instead of (print foo) and the inspected values would be available in a stack of inspectors. Alternatively I would trace the functions, and the tracer would record the passed and returned objects.


Slight tangent: Am I the only one that's a little bit annoyed that it seems like you need to start using the complex beast that is Emacs to get first class Lisp support? There are one-off apps, but everyone swears by SLIME mode or similar.

Yes, I get it. The editor itself is written in Lisp. It's the most hackable thing out there. Yes, fine. It is a masterpiece of technical achievement. That's all well and good, but...

I just want to learn the language, not the language and an editor that seems entirely alien at the same time. If I had to learn vim as a prerequisite to learning and writing Python (specifically using the debugger and a REPL), I probably would have never learned Python.


Try intellij with the cursive plugin for clojure. Tim Baldridge has a video or two on how to set it up.


It makes sense to use Lisp-specific tools with Lisp, otherwise the whole experience is underwhelming. The best Lisp tools are usually written in itself or some variant of it.

On the Mac you can download Clozure Common Lisp from Apple's Macintosh application store:

https://itunes.apple.com/us/app/clozure-cl/id489900618?mt=12

It comes with a small IDE written in itself.

A lot of people who use CL as a serious hobby or for work use one of the commercial development environments: Allegro CL or LispWorks.

There are several other (some are obscure) options, but many use GNU Emacs / Slime or its fork Sly.


I would love a proper "hobbyist" priced license for a full 64-bit LispWorks at around $200.

Stephen Wolfram did it for Mathematica (Home license)...


The LispWorks 64bit hobbyist license costs 600 Euro. It is not tied to a single machine and can run more then one process.


Agree. I've been using IDEA + Cursive, and it's great and easy to use and all that, but lately I'm starting to get tired of it -- it's just so heavy weight, to the point the entire IDE will sometimes hang on me for seconds at a time while I'm trying to enter code.

I've been thinking of trying out vim + fireplace (since I already know vim well enough). Emacs + CIDER will be a last resort as the learning curve for emacs is fairly steep, which is the reason I chose vi all those years ago when I was starting out with *nix and needed to choose an editor to learn :)


SLIME + Emacs user here. I always wondered if there is something similar to SLIME for Clojure, because soon I'll try Clojure as well, despite knowing the tortures of having to deal with the Java platform. Slime is awesome, however...

I can feel your pain, because that's what i thought when I first tried to use Emacs. My feelings ranged from "hideous horrible monstrosity" to "looks interesting but can't use it."

Really, after one or two days of using Emacs to write Common Lisp, Emacs became second-nature to me. And you start to appreciate how easy is to have a separate concept of "buffers" versus "windows". In short, arranging your screen to work quickly, is very easy and quick.

At the end, the shortcut keys for Emacs do have some sort of logic. Thus i present a

    *EMACS/SLIME KEYS PRIMER in 5 MINUTES, FOR SANE PEOPLE*
    in the insane world of Emacs
    -----------------------------------------------

    This assumes that you're running Emacs on a windowing system like X or
    Windows. Thus you have a nice menu bar that you can use with the
    mouse.

    The first you need to know is that "Meta" is the "alt" key. So "M-a"
    means "press Alt+a".

    The first key combination you need to know is "Control-H b" (_H_elp me
    with the _B_indings). This will make Emacs open a window which will
    tell you ALL the key mappings. That is, which key combination does
    which thing. Since this window is HUGE, press Control-S (_S_earch) and
    write the string you want to search, if you are looking for a
    particular command. Press Control-S several times until you locate
    it. To search in _R_everse, Control-R.

    The next key combination you need to know is "Control-G" (_G_o to
    hell). This cancels any command that is prompting you for input on the
    status bar.

    Now, the other thing you will need is to do COPY and PASTE. Easy to do
    this is with the "Insert" (Ins) key.  Control+Insert == COPY
    Shift+Insert == PASTE

    Now, the EMACS keys do have some logic, and this can help you remember
    them.

    Control-X for commands that have to do with Emacs itself, like editing
    commands. For example "Control-X U" is _U_ndo.

    "Control-X S" is _S_ave.

    Alt-X (called "Meta-X") executes a command, for which you write the
    name of the command. This can execute any of the thousand commands
    available on Emacs. Each command is basically a LISP function that you
    will invoke.  It is very simple to invoke a command, and if you don't
    know the full name of the command, you just write the beginning of the
    command, press TAB, and Emacs will list you all the available
    commands.

    So, if you want to start SLIME, the command is "slime". Alt-X "slime"
    ENTER.

    Once you're in SLIME mode, you have SLIME commands at your
    fingertips. Most of the SLIME commands start with "Control-C".

    So, for example to load and compile the current file into Common Lisp,
    you do: Control-C-K , which "K"ompiles the file.

    Or for example you want to compile just a small part of the code, for
    example a DEFUN you have wrote. Just select (using the mouse or
    keyboard) the complete definition (or part of the code) and press
    Control-C-C to "C"ompile that section. Now it is available so you can
    use that function/macro/etc on the REPL, which is the "buffer" called
    *slime-repl*.

    When you are writing code, SLIME auto-completes as much things as it
    can, you just need to press TAB to invoke the autocomplete. Also,
    SLIME will tell you what are the arguments of each function you call,
    as you are writing the code.  If you want, SLIME can also _S_upply all
    the required function/macro parameters, just press Control-C-S.

    There is also an ALTernate _I_ntelligent auto-complete, that you can
    invoke using Control-C ALT-I. See?

    Want to know which functions are in a package and describe a simple
    _D_ocumentation for each function in the _P_ackage? So, Control-C,
    Control-D, P.  Or want to look the Common Lisp _D_ocumentation for
    _H_elp? Control-C, Control-D, H. Etcetera.

    -------------------------------
    
SLIME is really an mind-opening experience for me, after having used MS Visual Studio, Visual Studio Code, Netbeans, Eclipse, and PyCharm. SLIME enables to work interactively, very quickly.

As for Emacs, final words are some wisdom words of HN user "Karunamon" which wrote:

"The editor itself is written in Lisp. It's the most hackable thing out there. Yes, fine. It is a masterpiece of technical achievement. "


  When it comes to debugging, I'm firmly in the printf camp; I rarely prefer debuggers over printf-based debugging
I'm struggling to understand this. Why would you ever prefer printf debugging in your local dev environment over using a debugger?


There is rarely a situation where you can't slip a printf or log message into a piece of code and observe how it's working in production. Depending on the complexity of the problem the debugger can also hide a lot of problems- is there a timing issue? is it intermittent? What happened the last 200k times? An interactive debugger technique doesn't help you at all if all you have is a coredump of an issue that happens once a month.

It is definitely a personal preference, but I just find myself in too many situations where the constraints to make a debugger in a dev environment work well end up adding a lot of complexity to the problem space. I know printf/write to a file works every time, everywhere.


I use both. They are useful in different situations. printf is lightweight, and can let you dump a lot of information very fast. It's a good approach when you're reasonably confident about what you need to know. A debugger gives you more flexibility about the questions you can ask on any particular run.


Language and environment independent. It just works. It's like IDEs -- so many people spend so much time writing and learning IDEs specific to a language rather than just picking a standard text editor that can be used for any language on any platform.


Yeah this. Does this thing colour parenthesis and stdlib builtins? We're good to go.


I have years working experience with Clojure. The tooling in Clojure is much better than this. If you want a debugger, Cursive has it out of the box, if you are used to Intellij, you can use it right away. If you are an Emacs user, then you know your way around CIDER and its tooling. It's not that I don't agree with the author's post. But I mean for simple functions, good enough tooling and unit tests are your friends.


I fear Lisp had already a better debugging experience in the mid to late 60s...


Clojure also has better debugging experience. This is a bare-bones workflow of a guy who doesn't use the best tools available in Clojure.


Better debugging experience than Common Lisp? Are you sure? Do you know what happens when you hit a runtime error in Common Lisp? You get a full backtrace of all the calls that lead to the error, the error code, the error explanation, and THEN a lot of options that you can do to make your code keep working "on the fly", for example the runtime environment will give you the following options:

   0: [KEEP-OLD] Keep symbols already accessible FOO (shadowing others).
   1: [TAKE-NEW] Make newly exposed symbols accessible in FOO, uninterning old ones.
   2: [RESOLVE-CONFLICT] Resolve conflict.
   3: [RETRY] Retry SLIME REPL evaluation request.
   4: [*ABORT] Return to SLIME's top level.
   5: [ABORT] Abort thread (#<THREAD "new-repl-thread" RUNNING {10060E47B3}>)


Could you provide links to this better debugger


Found them in other comments, I don't see anything that's native to clojure though, not like pdb is for python at least


Debugging, like most things in clojure, is a library. You can view the implementation (as an nREPL middleware) here[1]. It's possible to use this debug middleware from any random nrepl client, but I'm not aware of any that actually implement a command-line debugger such as you get with python's pdb. I suspect, but don't have any time or inclination to verify, that implementing a basic command-line debugger using this library would be a weekend hack. I suspect this debugger is far more useful integrated into your editor than stand-alone since clojure isn't really a file/line-oriented language (which would make selecting forms hard).

[1] https://github.com/clojure-emacs/cider-nrepl/blob/master/src...


Could you elaborate on this for newbies like me?


Interpreter (Clojure does not have one, so some debug functionality is painful to implement/use), readable error messages / stack traces, resident structure editor, clever break/trace packages, debug levels, ... were relatively sophisticated in, say, BBN Lisp.

See for example BBN Lisp manual from 1971:

http://www.softwarepreservation.org/projects/LISP/bbnlisp/Te...

See the chapter 9 on the editor, 15 on the break package, 16 on error handling, 17 on error correction, 19 on advising, 22 on the programmer's assistant, ...

In 1992 the developers of BBN Lisp / Interlisp got an ACM Software Systems award for:

> ... their pioneering work in programming environments that integrated source-language debuggers, fully compatible integrated interpreter/compiler, automatic change management, structure-based editing, logging facilities, interactive graphics, and analysis/profiling tools in the Interlisp system.


Read this 5-part short series: https://web.archive.org/web/20160304033445/http://malisper.m...

Having compile/break/continue/trace as part of the language standard makes for a nice built-in debugging experience independent of IDE, even if some dev envs are better than others.


Also worth noting that Cider (the most-commonly-used Clojure plugin for Emacs) has had a decent debugger for a couple years: https://github.com/clojure-emacs/cider/blob/master/doc/debug...

Cursive (Clojure for IntelliJ) does too.


interesting to see Eli as a famous Python lover started to try Clojure.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: