35

Debugging functional code is definitely more tricky than debugging imperative code. See discussions here, here and here. "Functional" debugging should support inspecting the return value of functions/closures/monads. Do any debuggers/IDEs have (plan to have) the ability to inspect intermediate return values?

For example, to debug this line in Scala, I should be able to step through 4 function invocations and inspect the returned value at each step before returning r

val r=(ls filter (_>1) sort (_<_) zipWithIndex) filter {v=>(v._2)%2==0} map{_._1}
Community
  • 1
  • 1
Adrian
  • 3,762
  • 2
  • 31
  • 40
  • Related-ish: http://stackoverflow.com/questions/268048/can-i-find-out-the-return-value-before-returning-while-debugging-in-visual-studio – Brian Nov 25 '10 at 01:17
  • UPDATE: There is a new thread on the scala internals mailing list that discusses design issues for the Scala Eclipse debugger. Very much related to this question. http://thread.gmane.org/gmane.comp.lang.scala.internals/4130 – Adrian Dec 02 '10 at 19:28

6 Answers6

37

I think everybody's advice to break this thing down to more manageable chunks is the best approach. One trick for debugging smaller expressions is to steal Ruby's tap function, as described here. "tap" allows you to stick an expression in the middle of a chain like this, and perhaps print out some debug values, like so:

val ls = List(1,2,3).map(_ * 2)
                .tap(soFar => println("So far: " + soFar))
                .map(_ * 2)
println(ls)

This will print out:

So far: List(2, 4, 6)
List(4, 8, 12)

It helps me every once in a while.

Adam Rabung
  • 5,232
  • 2
  • 27
  • 42
  • 7
    @missingfaktor: Officially, it's (a variant of) the K combinator. There's a very famous math book called *To Mock A Mockingbird* by Raymond Smullyan, which explains combinatory logic using birds, and this book indeed uses a Kestrel to represent the K combinator, and this terminology has spread even beyond the book. In short: Yes. – Jörg W Mittag Nov 25 '10 at 20:50
  • 2
    One of the problems here is that the debug metadata format of Java's `.class` files has simply no way to record debug metadata at a granularity smaller than a line. For functional code like this (even in Java(!)), you would need a way to express debug metadata at a (sub-)expression level. So, basically, you are stuck with `printf` debugging using `tap`, reformatting into multiple lines, and (temporarily) factoring sub-expressions into `val`s. – Jörg W Mittag Nov 25 '10 at 20:55
  • 6
    Going back to the cave debugging with println? Oh my. – monzonj Dec 06 '12 at 14:39
11

In a purely functional setting, stepping through is not nearly as useful as you might think. Since everything is composed of pure functions, you can just test those pieces individually using a process of elimination. In a lazy evaluation setting, stepping through code is even less useful.

Debugging programs in Haskell, for example, you would not at all be interested in tracing function calls. What you are interested in is a trace of intermediate function return values. It would be a very useful feature in any functional language to be able to give such a trace for any expression.

Apocalisp
  • 34,834
  • 8
  • 106
  • 155
  • 4
    Not sure I understand your point. Isn't "the gradual evaluation of an expression" the same thing as "inspecting intermediate function return values"? – Adrian Nov 25 '10 at 19:30
  • 2
    Yeah, not disagreeing with you, just pointing out that traditional IDE debug tools are totally useless. – Apocalisp Nov 27 '10 at 04:13
  • Im just curious how its not useful. If I have a map method with some advanced logic I want to test step by step, but I cant because I cant break into it, how is that not an issue? – user4779 Mar 24 '23 at 06:53
4

I know being concise is very nice, and I agree with you that IDEs should help with the debugging in these situations. But for the time being I have changed my coding style to assist with debugging. In my personal style I would have implemented your example as:

val noZeroLs = ls.filter(_>1)
val sortedLs = noZeroLs.sort(_<_)
val indexedNoZeroLs = sortedLs.zipWithIndex
val everySecondIndexedL = indexedNoZeroLs.filter(v => (v._2) % 2 == 0)
val everySecondL = everySecondIndexedL.map(_._1)

Coming up with meaningful names is difficult/laborious, but it does help you identify silly bugs; may help others to understand what is going on; and definitely helps with debugging.

Synesso
  • 37,610
  • 35
  • 136
  • 207
  • 1
    this code is much more difficult to understand that the one without intermediate values, there are much more things to keep track of... – gerferra Nov 25 '10 at 17:49
  • 4
    I agree this is more difficult to understand. Once you are familiar with Scala syntax, the "one-liners" are easier to understand. But your example makes me suggest a feature for IDEs/debuggers: What if I had an option to temporarily expand "one-liners" into something like your example while debuggers are running. This way I could step into the code and inspect intermediate function return values. Once the debugger finished the session, the code would revert to my original version. – Adrian Nov 25 '10 at 19:27
  • Damn I hate this programming trade-offs :) – Denis Mikhaylov Oct 24 '14 at 08:38
3

My approach to this problem is to break the expression down into parts binding the results to vals in the REPL. When I am satisfied I may even write a test case that does the same I did in the REPL so that I am sure things stay as I want and so that I or somebody else can come back later and see a more explicit version.

The ability to use the repl to explore coupled with nice and easy to use testing toolkits has made debuggers all but obsolete for me.

Of course YMMV.

fedesilva
  • 1,532
  • 13
  • 12
2

Starting Scala 2.13, the chaining operation tap, as mentioned in Adam Rabung's answer, has been included in the standard library, and can be used for debugging by printing intermediate versions of a pipeline:

import scala.util.chaining._

scala> val ls = List(1,2,3).map(_ * 2).tap(println).map(_ * 2).tap(println)
List(2, 4, 6)
List(4, 8, 12)
ls: List[Int] = List(4, 8, 12)

The tap chaining operation applies a side effect (in this case println) on a value (in this case a List) while returning the original value:

def tap[U](f: (A) => U): A

Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
1

If you don't have an IDE, you can still use this tool I wrote:

https://github.com/JohnReedLOL/scala-trace-debug

To print the intermediary values, you can take this example:

val result=(lists.filter(_>1).sort(_<_).zipWithIndex).filter{v=>(v._2)%2==0}.map{_._1}

And add traces to it:

import scala.trace.implicitlyTraceable
val result=(lists.filter(_>1).out.sort(_<_).println.zipWithIndex).filter{v=>(v._2)%2==0}.out.map{_._1}.out

An implicit conversion allows you to print.

Michael Lafayette
  • 2,972
  • 3
  • 20
  • 54