I still have a pretty good soft spot for Standard ML. Haskell may be sexier, but whenever I want to get something serious done, I find myself turning to SML.

One of my occasional claims for why I sick with ML is that you can actually debug SML programs (c.f., Haskell, where being laziness makes debugging "interesting" -- great if you want a research project).

But in practice, debugging in SML/NJ can actually be a pain. If you get an exception from one of the library functions, you may end up with an unhelpful error message like this one:

    uncaught exception Domain [domain error]
      raised at: Basis/Implementation/real64.sml:88.32-88.46

Without any sort of backtrace, you get no clue about where/how the exception was raised.

But it turns out that there is a feature in SML/NJ that lets you get a backtrace. It's just that it's barely documented at all!

It turns out that if you type:

    CM.make "$smlnj-tdp/back-trace.cm";
    SMLofNJ.Internals.TDP.mode := true;

when you first start SML, and then compile your code, when you get an exception, you'll get a backtrace.

Now, you'll see something more like:

    CALL   art.sml:52.7-52.55: Art.toIntensity[2]
              (from: art.sml:89.38-89.57: Art.emitGray[2].iz)
    CALL   art.sml:79.27-93.33: Art.emitGray[2]
              (from: art.sml:13.26-13.29: Art.for[2])
    GOTO   art.sml:10.7-13.45: Art.for[2]
              (from: art.sml:78.22-93.34: Art.emitGray[2])
    CALL   art.sml:77.19-93.34: Art.emitGray[2]
              (from: art.sml:13.26-13.29: Art.for[2])
    CALL   art.sml:10.7-13.45: Art.for[2]
              (from: art.sml:75.14-93.35: Art.emitGray[2])
    CALL   art.sml:64.7-98.7: Art.emitGray[2]
              (from: ???)
    CALL   art.sml:249.7-307.9: Art.doMix[2]
              (from: ???)
    
    uncaught exception Domain [domain error]
      raised at: Basis/Implementation/real64.sml:88.32-88.46

Cool. You've got to wonder though, why people would write a cool and useful feature like this and not clearly tell people about it.