116

...or is it just a practice?

I'm asking this because of an argument with my professor: I lost credit for calling a function recursively on the basis that we did not cover recursion in class, and my argument is that we learned it implicitly by learning return and methods.

I'm asking here because I suspect someone has a definitive answer.

For example, what is the difference between the following two methods:

public static void a() {
    return a();
    }

public static void b() {
    return a();
    }

Other than "a continues forever" (in the actual program it is used correctly to prompt a user again when provided with invalid input), is there any fundamental difference between a and b? To an un-optimized compiler, how are they handled differently?

Ultimately it comes down to whether by learning to return a() from b that we therefor also learned to return a() from a. Did we?

Áxel Costas Pena
  • 5,886
  • 6
  • 28
  • 59
  • 1
    It is not a feature of the language (JLS does not mention recursive method calls). It is concept on how to solve things. Or if you want it that way, it's a classification of methods. – Absurd-Mind May 10 '14 at 18:23
  • 3
    I would argue that unless the question is to use iteration to solve a problem, I don't see anything wrong with using recursion. In fact recursion is more elegant. Recursion is one of the primary constructs one deals with when using functional languages. –  May 10 '14 at 18:28
  • 3
    @kadaj I agree, it is much more elegant than, say, `while(foo)` wrapped around the near-entirety of the method. Our assignment did not specify to use iteration, simply to perform a task by whatever means covered. –  May 10 '14 at 18:36
  • 24
    Excellent debate. I wonder if you explained it like this to your professor. If you did, I think he should give you your lost credit. – Michael Yaworski May 10 '14 at 18:46
  • 58
    Recursion isn't even a concept exclusive to computer science. The Fibonacci function, the factorial operator and lots of other things from mathematics (and possibly other fields) are (or at least can be) expressed recursively. Does the professor demand that you're oblivious to these things as well? – Theodoros Chatzigiannakis May 10 '14 at 18:46
  • 34
    The professor should give him extra credit instead, for coming up with an elegant way to solve a problem, or for say out of the box thinking. –  May 10 '14 at 18:54
  • 11
    What was the assignment? This is a problem I have often wondered about, when you submit a programming assignement, what is being marked, your ability to solve a problem or your ability to use what you have learnt. These two are not necessarily the same. – Leon May 10 '14 at 18:57
  • 1
    @Leon Our final assignment, a game of Tic-Tac-Toe. Recursion was used if the player marked a spot already marked, as we were instructed to prompt for input until a valid move is made. –  May 10 '14 at 19:00
  • 4
    Using input inside a recursive function is something I also did at college, I got my marks :) Certainly now I would not recommend it, but in my opinion, if an assignment does not specify what approach to use then all valid solutions should be seen as correct. On the other hand it's your professor's class and he may feel that you should use an approach for repetition that has been covered by the course. Like I said in my previous comment, it's sometimes difficult to know what is actually being tested with a programming assignemnt. – Leon May 10 '14 at 19:18
  • 3
    https://imgflip.com/i/8pe7c – Henrik Erlandsson May 10 '14 at 21:19
  • 2
    @TheodorosChatzigiannakis: mathematical recursion isn't really the same thing as recursion in a imperative programming language; the existence of the one does not mandate the existence of the other. – Harry Johnston May 11 '14 at 02:27
  • a->a is the most correct option **if** there is absolutely no context surrounding the recursion, so b->a->a is an abstraction. In the latter case, the only difference is that b is under a->a on the stack. I think you professor shouldn't be too hard on what he considers best practise. – toplel32 May 11 '14 at 11:09
  • 35
    FWIW, prompting for input until it's right is not a good place to use recursion, it's too easy to overflow the stack. For this particular instance, it would be better to use something like `a() { do { good = prompt(); } while (!good); }`. – Kevin May 11 '14 at 15:27
  • @JMK: that is this question... (Or have question been merged in the meantime?). That said, there must be a duplicate somewhere. How can there not be nearly 6 years after Stack Overflow launched? – Peter Mortensen May 11 '14 at 15:30
  • @PeterMortensen That's the joke. – Jack Henahan May 11 '14 at 15:37
  • 1
    Inspired by gnasher729's answer, can we say that "recursion is a feature of Java because it's a thing that some other languages like FORTRAN don't support"? It still seems weird to justify taking off points for that reason, but I'd just think that if there are languages that support function calls (or similar) but not recursion, then recursion could be considered a feature. – Mark S. May 11 '14 at 16:27
  • @ABsurd: "It is not a feature of the language (JLS does not mention recursive method calls)." - the support is implicit. But you *certainly* need support for recursion! Matter of fact we used to have programming languages that stored local variables of functions in global (maybe thread-local, but threads weren't such a big deal in the good old days) storage. Fortran 90 I'm pretty sure still needs a special `recursive` keyword for such functions (and 77 didn't support them at all for an example). – Voo May 11 '14 at 18:02
  • Another reason I can think of to take away marks is that the instructor may have believed, based on an apparently inappropriate use of recursion, that this student thought it worked like a `goto`, and just didn't want to say so outwardly - so he just fell back on a lame but less controversial excuse. – Aaronaught May 11 '14 at 18:55
  • @Voo: I suspect that if we looked carefully into the specifications for the Java bytecode it would be possible to show that Java VMs are obliged to support recursion. (I may be wrong, of course; I haven't tried.) – Harry Johnston May 11 '14 at 20:33
  • If you were docked for using recursion, that's probably because you used recursion in a place where it was not the appropriate solution. In general you should resort to recursion only for case that can not be solved iteratively -- or whose iterative solution would force you to maintain a stack and thus become pseudo-recursion. – keshlam May 11 '14 at 21:43
  • 2
    @keshlam There's a lot of problems that can be solved even without maintaining a stack themselves that are still much easier to solve recursively. Simple examples that come to mind are things like finding all combinations of a set or tree traversal. – Voo May 11 '14 at 22:07
  • 1
    Perhaps the professor *would* have given him extra credit for providing a solution that used recursion in addition to a solution using the methods already covered. The lesson here is to be careful to give your professors the evidence they need to declare you a great student, as well as an out-of-the-box thinker. – Russia Must Remove Putin May 11 '14 at 23:24
  • 1
    @voo: Granted, but generally if you don't know that you need recursion you know that you don't need it. – keshlam May 11 '14 at 23:37
  • 2
    Recursion is not a feature; it's a bug. In particular, a *stack overflow*. – R.. GitHub STOP HELPING ICE May 12 '14 at 02:23
  • You used linear and indirect recursion here directly can't be called as you wanted to use recursion, this means you are going in right direction,and your professor should give you credits. – vITs May 12 '14 at 07:25
  • @R So by that argument `new` isn't a feature, it's a bug. In particular an `out of memory exception`? – Voo May 12 '14 at 12:39
  • 1
    Quoting the quote from my professors signature: "Those who can do, those who can't teach". – softarn May 12 '14 at 12:54
  • @Voo: In the case of `new` you can handle the exception. I'm not an expert on Java so perhaps I'm missing something clever it does, but it's generally not possible to handle stack overflows because the exception handler would itself need stack space. – R.. GitHub STOP HELPING ICE May 12 '14 at 13:44
  • @R. Since the exception handler itself unwinds the stack, assuming you don't have the handler around each call you'll have more than enough stack space to handle it. An `OutOfMemoryError` on the other hand means that you really can't allocate any more memory in basically all situations and you can't do much in Java without dynamic memory allocation (especially true since the handler will run interpreted so no escape analysis, et al.) – Voo May 12 '14 at 14:37
  • Using recursion in this aspect (i.e. continuous loop) is the *wrong* method to use -- I am not saying that recursion is wrong, just wrong for this purpose. When you do infinite recursion, each pass must retain resources that are never freed, and eventually your system runs out of resources, unless your compiler is smart enough to optimize it away (i.e. if you are using tail recursion as is probably the case here) - in that case it gets turned into a proper iterative loop. Your prof is correct in deducting your credit because using recursion here is wrong. – Stephen Chung May 14 '14 at 02:46
  • This video https://www.youtube.com/watch?v=FITJMJjASUs and this article https://mvanier.livejournal.com/2897.html might interest you. – Fabian Zeindl May 15 '14 at 21:46

9 Answers9

114

To answer your specific question: No, from the standpoint of learning a language, recursion isn't a feature. If your professor really docked you marks for using a "feature" he hadn't taught yet, that was wrong.

Reading between the lines, one possibility is that by using recursion, you avoided ever using a feature that was supposed to be a learning outcome for his course. For example, maybe you didn't use iteration at all, or maybe you only used for loops instead of using both for and while. It's common that an assignment aims to test your ability to do certain things, and if you avoid doing them, your professor simply can't grant you the marks set aside for that feature. However, if that really was the cause of your lost marks, the professor should take this as a learning experience of his or her own- if demonstrating certain learning outcomes is one of the criteria for an assignment, that should be clearly explained to the students.

Having said that, I agree with most of the other comments and answers that iteration is a better choice than recursion here. There are a couple of reasons, and while other people have touched on them to some extent, I'm not sure they've fully explained the thought behind them.

Stack Overflows

The more obvious one is that you risk getting a stack overflow error. Realistically, the method you wrote is very unlikely to actually lead to one, since a user would have to give incorrect input many many times to actually trigger a stack overflow.

However, one thing to keep in mind is that not just the method itself, but other methods higher or lower in the call chain will be on the stack. Because of this, casually gobbling up available stack space is a pretty impolite thing for any method to do. Nobody wants to have to constantly worry about free stack space whenever they write code because of the risk that other code might have needlessly used a lot of it up.

This is part of a more general principle in software design called abstraction. Essentially, when you call DoThing(), all you should need to care about is that Thing is done. You shouldn't have to worry about the implementation details of how it's done. But greedy use of the stack breaks this principle, because every bit of code has to worry about how much stack it can safely assume it has left to it by code elsewhere in the call chain.

Readability

The other reason is readability. The ideal that code should aspire to is to be a human-readable document, where each line describes simply what it's doing. Take these two approaches:

private int getInput() {
    int input;
    do {
        input = promptForInput();
    } while (!inputIsValid(input))
    return input;
}

versus

private int getInput() {
    int input = promptForInput();
    if(inputIsValid(input)) {
        return input;
    }
    return getInput();
}

Yes, these both work, and yes they're both pretty easy to understand. But how might the two approaches be described in English? I think it'd be something like:

I will prompt for input until the input is valid, and then return it

versus

I will prompt for input, then if the input is valid I will return it, otherwise I get the input and return the result of that instead

Perhaps you can think of slightly less clunky wording for the latter, but I think you'll always find that the first one is going to be a more accurate description, conceptually, of what you are actually trying to do. This isn't to say recursion is always less readable. For situations where it shines, like tree traversal, you could do the same kind of side by side analysis between recursion and another approach and you'd almost certainly find recursion gives code which is more clearly self-describing, line by line.

In isolation, both of these are small points. It's very unlikely this would ever really lead to a stack overflow, and the gain in readability is minor. But any program is going to be a collection of many of these small decisions, so even if in isolation they don't matter much, it's important to learn the principles behind getting them right.

Ben Aaronson
  • 6,955
  • 2
  • 23
  • 38
  • 8
    Can you expand on your assertion that recursion isn't a feature? I've argued in my answer that it is, because not all compilers necessarily support it. – Harry Johnston May 11 '14 at 02:24
  • @HarryJohnston I don't think I can give a completely rigorous answer since it's hard to come up with a rigorous definition of a "feature". I don't think the hypothetical possibility of a compiler not supporting it would be my definition though, especially when- as you've pointed out- it's such an unlikely scenario in reality. In particular, for a student or teacher in a class, what counts as a feature is much more likely to be from the perspective of somebody writing code. I can't imagine a teacher designing their syllabus based on compiler implementations for example – Ben Aaronson May 11 '14 at 02:31
  • @HarryJohnston From the point of view of somebody writing code, I'd argue that "methods can call methods but not themselves" would actually be *more* complicated an idea than "methods can call methods". A somewhat exaggerated analogy would be if the professor docked marks for doing `int b = a + 0;`, because while he'd taught integer addition, he hadn't specifically taught adding 0 yet. Unless you give a reason to think that 0 *isn't* a valid integer to add, or that methods *can't* call themselves, they seem like the default assumption to make based on what is already known. – Ben Aaronson May 11 '14 at 02:35
  • 5
    Not all languages necessarily support recursion either, so it isn't necessarily just a question of choosing the right compiler - but you're quite right to say that "feature" is an inherently ambiguous description, so fair enough. Your second point is also fair from the perspective of someone learning to program (as is now usual) without having any background in machine code programming first. :-) – Harry Johnston May 11 '14 at 02:38
  • @HarryJohnston Point taken though, I've edited the answer to qualify the assertion a little – Ben Aaronson May 11 '14 at 02:43
  • 2
    Note that the 'readability' problem is a problem with the syntax. There is nothing inherently "unreadable" about recursion. In fact, induction is the easiest way to express inductive data structures, like loops and lists and sequences and so on. And most data structures are inductive. – nomen May 11 '14 at 06:52
  • @nomen I agree there's nothing inherently unreadable about recursion, and for something like traversing a tree it expresses very clearly what you're actually doing. I'm not so sure I agree for cases like this where it's just being used for simple iteration though. I think even if there was a `recurse` keyword or some other nice syntactic sugar, it would always more or less boil down to the second "plain English" description I gave for it above. – Ben Aaronson May 11 '14 at 08:50
  • @Ben: You "read" recursion as "the nth foo is 'some operation' on the n-1st foo", or intuitively, "the next foo is the last foo under some operation". You don't describe what a recursive function *does*, you describe what a recursively defined value *is*. This does require shifting perspective to using "nouns" to describe programs instead of "verbs". And that's fine. You're missing out if you can't do both. Queries are for asking computers for values. Programs are for constructing values. But they are just different vocabularies. – nomen May 11 '14 at 15:49
  • Readability as a full out negative of recursion without any caveats? Write the comparable code for traversing a tree iteratively and recursively and you can make exactly the opposite argument. Hell, if we need general statements I will say that recursion usually leads to a clearer solution that allows you to much easier state pre- and postconditions as well as invariants, which in turn makes the code not only more readable but also more likely to be correct. – Voo May 11 '14 at 18:09
  • @Voo As I said in previous comments, that's not what I meant. It's only less readable in this situation. For tree traversal, for example, it's definitely more readable. I thought this was relatively clear from my answer, but I've edited it to hopefully leave no room for misunderstanding. – Ben Aaronson May 11 '14 at 18:37
  • I agree that the recursive version of this particular task is less readable; it's a stupid thing for an instructor to dock marks just for using it because it wasn't covered, but it *would* make sense to dock marks for using recursion when it's both *unnecessary* and *sub-optimal*. He's 0 for 2 here; it's a task that's trivially easy to do in a loop, and recursion wastes stack frames (what if the input was piped in from a file? An overflow is definitely possible!) and obfuscates the true intent. If I were the instructor, I might think he was trying, unsuccessfully, to show off. – Aaronaught May 11 '14 at 18:46
  • @nomen Perhaps I'm thinking of readability on a more fine-grained scale than what you're talking about? The level of readability I mean is line by line, where ideally *every single line* should be as clear about its intent as possible. In this sense you have to get through several lines- the entire method- before you even realise the function *is* recursive, then do a complete mental shift to thinking in the terms you're describing. – Ben Aaronson May 11 '14 at 18:51
  • @Ben: I'm not sure where you're going with "line by line". As far as line by line readability goes, what does it matter if it is recursive or just calls a different method? Surely the latter would not confuse you. If anything, recursion of that sort is simpler than calling a different method, since there is one less method to put into your mental model. – nomen May 11 '14 at 19:46
  • @mintchkin: the OP did provide context: "in the actual program it [recursion] is used correctly to prompt a user again when provided with invalid input" and "Recursion was used if the player marked a spot already marked, as we were instructed to prompt for input until a valid move is made." – Harry Johnston May 11 '14 at 20:18
  • @nomen Well understanding a line isn't just about understanding literally what it's doing, it's also about understanding what its actual intention is. For example, I'd argue that assigning magic numbers to well-named variables improves readability (though its main benefit is being DRY). Because even though `if(cardsInHand < 7)` and `if(cardsInHand < handCapacity)` are both very easy to understand what they're actually doing, the latter is much easier to understand intent. – Ben Aaronson May 11 '14 at 21:56
  • @nomen Anyway, I think I expressed why I think readability is worse with recursion in this case better in the answers than I have been in comments, so I'll probably leave it at that. – Ben Aaronson May 11 '14 at 21:57
  • 6
    I think you've stacked the deck with your wording. You described the iterative version functionally and vice-versa. I think a fairer line-by-line description of both would be “I will prompt for input. If the input is not valid, I will keep repeating the prompt until I get valid input. Then I will return it.” vs “I will prompt for input. If the input is valid, I will return it. Otherwise I will return the result of a do-over.” (My kids understood the functional concept of a do-over when they were in pre-school, so I think it's a legitimate English summary of the recursive concept here.) – pjs May 11 '14 at 23:09
  • @pjs I quite like the "do-over" wording. I don't agree with your wording of the iterative version though, since make two mentions of both the prompt and the validation, and I can't see how you'd get that from a line-by-line reading where they both appear only once. The only bit I feel I may have cheated slightly is replacing "while not x" with "until x" – Ben Aaronson May 11 '14 at 23:33
  • @BenAaronson The two mentions are because it's important to define the scope of what's being repeated, and the fact that it *is* being repeated procedurally. – pjs May 12 '14 at 14:18
  • @pjs Right, I'm just not sure that's the simplest line by line reading. If I say "keep digging until the hole is large enough", that very closely mirrors `do { Dig();} while(!HoleLargeEnough())`. You wouldn't say "dig, then if the hole isn't large enough, keep repeating the digging until the hole is large enough." Translating that version into code would be more like `Dig(); if(!HoleLargeEnough()){do { Dig();} while(!HoleLargeEnough())}` – Ben Aaronson May 12 '14 at 14:31
  • @BenAaronson It is a conundrum. When teaching, I often try to avoid restating everything while retaining the repetitious spirit by saying "lather, rinse, repeat." That seems to amuse the students. – pjs May 12 '14 at 14:36
  • 2
    @HarryJohnston Lacking support for recursion would be an *exception* to existing features rather than a lack of a new feature. In particular, in the context of this question, a "new feature" means "useful behavior we haven't yet taught exists", which is not true of recursion because it's a logical extension of features that *were* taught (namely, that procedures contain instructions, and procedure calls are instructions). It's as if the professor taught a student addition, and then scolded him for adding the same value more than once because "we haven't covered multiplication". – nmclean May 12 '14 at 15:16
  • @nmclean: IMO, a sufficiently thoughtful student would realize that calling a procedure from itself is a special case which might or might not be permitted. That's not true of addition, though a mathematician might rightly criticize someone writing a+b+c instead of (a+b)+c if associativity hasn't yet been established. However, you'll note that I've already conceded that the OP might have meant "feature" to be interpreted subjectively rather than in the more usual sense (broadly the same as "characteristic", "capability", or "behaviour"). – Harry Johnston May 12 '14 at 21:08
  • 1
    I don't buy that iteration is always more readable than recursion. Please explain a Fibonacci series with out using a recursive definition – Rune FS May 13 '14 at 20:15
  • To the point of whether the professor was right in not granting points well that should be down to whether or not the problem he posed was solved correctly not _how_ it was solved correctly. He's not training test monkeys but computer scientists – Rune FS May 13 '14 at 20:17
  • 1
    @RuneFS Well I did already edit the question to make it clear that I only mean iteration is more readable than recursion *in this situation*. Not sure how much more clear I can make it without repeating myself unduly! – Ben Aaronson May 13 '14 at 23:37
  • I agree you did but my point was broader than that. I have an FP background where loops are odd bastards and recursion is the norm. You read and understand code based on your mental model. I do it based on my and we're all different. The difference that you have in your example is based on your mental model not recursion. `if(inputIsValid()) return input; return getInput();` is a recursive version that to be is almost English. If input is valid then return the input otherwise get the input. And it's more declarative that how you described your first example in _my_ view – Rune FS May 14 '14 at 08:14
  • @RuneFS Fair enough, I certainly don't want to claim I'm the ultimate arbitrator of readability or anything. I'm going to edit how I described the recursive version slightly, hopefully to make it a little more consistent. – Ben Aaronson May 14 '14 at 09:38
  • To me the recursive version you have is read `prompt for input, if it's valid then return otherwise get (valid) input)` and the other is `declare input, prompt for input. while not input is valid keep prompting. (when input is valid) return input` Not saying it's more correct simple that there's no correct here and thus you can't argue that one is better than the other. – Rune FS May 14 '14 at 10:25
  • @RuneFS Well I'd say you've stacked the deck a bit against the second version there. You wrote the first in good, conversational english ("if it's valid" rather than "if valid") but not the second ("while not input is valid"). Also the bits in parentheses serve to add useful clarification the first one whereas they add unnecessary complication to the second. Anyway, ultimately I agree that it's subjective, but it's also somewhat a function of the language. What's most readable for Java may not be for F# or SQL or assembly language. – Ben Aaronson May 14 '14 at 10:37
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/52662/discussion-between-rune-fs-and-ben-aaronson) – Rune FS May 14 '14 at 10:41
48

To answer the literal question, rather than the meta-question: recursion is a feature, in the sense that not all compilers and/or languages necessarily permit it. In practice, it is expected of all (ordinary) modern compilers - and certainly all Java compilers! - but it is not universally true.

As a contrived example of why recursion might not be supported, consider a compiler that stores the return address for a function in a static location; this might be the case, for example, for a compiler for a microprocessor that does not have a stack.

For such a compiler, when you call a function like this

a();

it is implemented as

move the address of label 1 to variable return_from_a
jump to label function_a
label 1

and the definition of a(),

function a()
{
   var1 = 5;
   return;
}

is implemented as

label function_a
move 5 to variable var1
jump to the address stored in variable return_from_a

Hopefully the problem when you try to call a() recursively in such a compiler is obvious; the compiler no longer knows how to return from the outer call, because the return address has been overwritten.

For the compiler I actually used (late 70s or early 80s, I think) with no support for recursion the problem was slightly more subtle than that: the return address would be stored on the stack, just like in modern compilers, but local variables weren't. (Theoretically this should mean that recursion was possible for functions with no non-static local variables, but I don't remember whether the compiler explicitly supported that or not. It may have needed implicit local variables for some reason.)

Looking forwards, I can imagine specialized scenarios - heavily parallel systems, perhaps - where not having to provide a stack for every thread could be advantageous, and where therefore recursion is only permitted if the compiler can refactor it into a loop. (Of course the primitive compilers I discuss above were not capable of complicated tasks like refactoring code.)

Harry Johnston
  • 35,639
  • 6
  • 68
  • 158
  • For example, the C preprocessor doesn't support recursion in macros. Macros definitions behave similarly to functions, but you can't call them recursively. – sth May 11 '14 at 10:46
  • 7
    Your "contrived example" isn't all that contrived: The Fortran 77 standard did not allow functions to call themselves recursively-- the reason being pretty much what you describe. (I believe the address to jump to when the function was done was stored at the end of the function code itself, or something equivalent to this arrangement.) See **[here](http://www.esm.psu.edu/~ajm138/fortranexamples.html)** for a bit about that. – alexis May 11 '14 at 18:02
  • 5
    Shader languages or GPGPU languages (say, GLSL, Cg, OpenCL C) don't support recursion, as an example. Insofar, the "not all languages support it" argument is certainly valid. Recursion presumes the equivalent of a stack (it needs not necessarily be a _stack_, but there needs to be a means to store return addresses and function frames _somehow_). – Damon May 11 '14 at 20:19
  • A Fortran compiler I worked on a bit in the early 1970's did not have a call stack. Each subroutine or function had static memory areas for the return address, parameters, and its own variables. – Patricia Shanahan May 12 '14 at 03:27
  • 2
    Even some versions of Turbo Pascal disabled recursion by default, and you had to set a compiler directive to enable it. – dan04 May 12 '14 at 05:55
17

The teacher wants to know whether you have studied or not. Apparently you didn't solve the problem the way he taught you (the good way; iteration), and thus, considers that you didn't. I'm all for creative solutions but in this case I have to agree with your teacher for a different reason:
If the user provides invalid input too many times (i.e. by keeping enter pressed), you'll have a stack overflow exception and your solution will crash. In addition, the iterative solution is more efficient and easier to maintain. I think that's the reason your teacher should have given you.

mike
  • 1,956
  • 16
  • 22
  • 2
    We weren't told to perform this task in any specific way; and we learned about methods, not just iteration. Also, I'd leave which one is easier to read up to personal preference: I chose what looked good to me. The SO error is new to me, though the idea that recursion is a feature in and of itself still doesn't seem founded. –  May 10 '14 at 19:51
  • 3
    "I'd leave which one is easier to read up to personal preference". Agreed. Recursion is not a Java feature. [These](http://www.oracle.com/us/technologies/java/features/index.html) are. – mike May 10 '14 at 20:07
  • Would a descent compiler likely optimize out the recursion in trivial cases? I know GCC is able to do this, including it's java compiler but am not sure about the sun/oracle compiler. This would prevent the possibility of stack overflow altogether. There are languages which need the maximum call depth to be known at compile time. – Vality May 11 '14 at 00:11
  • 2
    @Vality: Tail call elimination? Some JVMs might do it, but keep in mind it also needs to maintain a stack trace for exceptions. If it allows tail call elimination, the stack trace, generated naïvely, may become invalid, so some JVMs do not do TCE for that reason. – icktoofay May 11 '14 at 02:56
  • 5
    Either way, relying on optimization to make your broken code less broken, is pretty bad form. – cHao May 11 '14 at 06:06
  • 7
    +1, see that in Ubuntu recently the login screen was broken when the user hit the Enter button continuously, same happend to the XBox – Sebastian May 11 '14 at 20:59
14

Deducting points because "we didn't cover recursion in class" is awful. If you learnt how to call function A which calls function B which calls function C which returns back to B which returns back to A which returns back to the caller, and the teacher didn't tell you explicitly that these must be different functions (which would be the case in old FORTRAN versions, for example), there is no reason that A, B and C cannot all be the same function.

On the other hand, we'd have to see the actual code to decide whether in your particular case using recursion is really the right thing to do. There are not many details, but it does sound wrong.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
10

There are many point of views to look at regarding the specific question you asked but what I can say is that from the standpoint of learning a language, recursion isn't a feature on its own. If your professor really docked you marks for using a "feature" he hadn't taught yet, that was wrong but like I said, there are other point of views to consider here which actually make the professor being right when deducting points.

From what I can deduce from your question, using a recursive function to ask for input in case of input failure is not a good practice since every recursive functions' call gets pushed on to the stack. Since this recursion is driven by user input it is possible to have an infinite recursive function and thus resulting in a StackOverflow.

There is no difference between these 2 examples you mentioned in your question in the sense of what they do (but do differ in other ways)- In both cases, a return address and all method info is being loaded to the stack. In a recursion case, the return address is simply the line right after the method calling (of course its not exactly what you see in the code itself, but rather in the code the compiler created). In Java, C, and Python, recursion is fairly expensive compared to iteration (in general) because it requires the allocation of a new stack frame. Not to mention you can get a stack overflow exception if the input is not valid too many times.

I believe the professor deducted points since recursion is considered a subject of its own and its unlikely that someone with no programming experience would think of recursion. (Of course it doesn't mean they won't, but it's unlikely).

IMHO, I think the professor is right by deducting you the points. You could have easily taken the validation part to a different method and use it like this:

public bool foo() 
{
  validInput = GetInput();
  while(!validInput)
  {
    MessageBox.Show("Wrong Input, please try again!");
    validInput = GetInput();
  }
  return hasWon(x, y, piece);
}

If what you did can indeed be solved in that manner then what you did was a bad practice and should be avoided.

CodeMonkey
  • 11,196
  • 30
  • 112
  • 203
  • The purpose of the method itself is to validate the input, then call and return the result of _another_ method (that's why it returns itself). To be specific, it checks if the move in a Tic-Tac-Toe game is valid, then returns `hasWon(x, y, piece)` (to only check the affected row and column). –  May 10 '14 at 18:46
  • you could easily just take the validation part ONLY and put it in another method called "GetInput" for example and then use it like I wrote in my answer. I have edited my answer with how it should look like. Of course you can make GetInput return a type which holds the information you need. – CodeMonkey May 10 '14 at 18:48
  • 1
    Yonatan Nir: When was recursion a bad practice? Maybe JVM will blow up because the Hotspot VM couldn't optimize because of byte code security and stuff would be a nice argument. How is your code any different other than it uses a different approach? –  May 10 '14 at 18:52
  • 1
    Recursion is not always a bad practice but if it can be avoided and keep the code clean and easy to maintain then it should be avoided. In Java, C, and Python, recursion is fairly expensive compared to iteration (in general) because it requires the allocation of a new stack frame. In some C compilers, one can use a compiler flag to eliminate this overhead, which transforms certain types of recursion (actually, certain types of tail calls) into jumps instead of function calls. – CodeMonkey May 10 '14 at 19:04
  • @kadaj and my code is different since it doesn't use recursion but gets the job done with clarity. – CodeMonkey May 10 '14 at 19:12
  • 1
    It's not clear, but if you replaced a loop with indefinite number of iterations with recursion, then that's bad. Java does not guarantee tail call optimization, so you might easily run out of stack space. In Java, don't use recursion, unless you are guaranteed to have a limited amount of iterations (usually logarithmic compared to total size of data). – hyde May 11 '14 at 12:30
6

Maybe your professor hasn't taught it yet, but it sounds like you're ready to learn the advantages and disadvantages of recursion.

The main advantage of recursion is that recursive algorithms are often much easier and quicker to write.

The main disadvantage of recursion is that recursive algorithms can cause stack overflows, since each level of recursion requires an additional stack frame to be added to the stack.

For production code, where scaling can result in many more levels of recursion in production than in the programmer's unit tests, the disadvantage usually outweighs the advantage, and recursive code is often avoided when practical.

Warren Dew
  • 8,790
  • 3
  • 30
  • 44
  • 1
    Any potentially risky recursive algorithm can always be trivially rewritten to use an explicit stack - the call stack is, after all, just a stack. In this case, if you rewrote the solution to use a stack, it would look ridiculous - further evidence that the recursive answer is not a very good one. – Aaronaught May 11 '14 at 18:52
  • 1
    If stack overflows are a problem, you should use a language/runtime which supports tail call optimisation, such as .NET 4.0 or any functional programming language – Sebastian May 11 '14 at 20:57
  • Not all recursions are tail calls. – Warren Dew Jun 06 '16 at 04:14
6

Regarding the specific question, is recursion a feature, I'm inclined to say yes, but after re-interpreting the question. There are common design choices of languages and compilers that make recursion possible, and Turing-complete languages do exist that don't allow recursion at all. In other words, recursion is an ability that is enabled by certain choices in language/compiler design.

  • Supporting first-class functions makes recursion possible under very minimal assumptions; see writing loops in Unlambda for an example, or this obtuse Python expression containing no self-references, loops or assignments:

    >>> map((lambda x: lambda f: x(lambda g: f(lambda v: g(g)(v))))(
    ...   lambda c: c(c))(lambda R: lambda n: 1 if n < 2 else n * R(n - 1)),
    ...   xrange(10))
    [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
    
  • Languages/compilers that use late binding, or that define forward declarations, make recursion possible. For example, while Python allows the below code, that's a design choice (late binding), not a requirement for a Turing-complete system. Mutually recursive functions often depend on support for forward declarations.

    factorial = lambda n: 1 if n < 2 else n * factorial(n-1)
    
  • Statically typed languages that allow recursively defined types contribute to enabling recursion. See this implementation of the Y Combinator in Go. Without recursively-defined types, it would still be possible to use recursion in Go, but I believe the Y combinator specifically would be impossible.

wberry
  • 18,519
  • 8
  • 53
  • 85
  • 1
    This made my head explode, especially the Unlambda +1 – John Powell May 13 '14 at 22:35
  • Fixed-point combinators are hard. When I decided to learn functional programming, I forced myself to study the Y combinator until I understood it, and then apply it to write other useful functions. Took me a while, but was well worth it. – wberry May 15 '14 at 21:44
5

From what I can deduce from your question, using a recursive function to ask for input in case of input failure is not a good practice. Why?

Because every recursive functions call gets pushed on to the stack. Since this recursion is driven by user input it is possible to have an infinite recursive function and thus resulting in a StackOverflow :-p

Having a non recursive loop to do this is the way to go.

remudada
  • 3,751
  • 2
  • 33
  • 60
  • The bulk of the method in question, and the purpose of the method itself, is to validate input through various checks. The process begins all over again if the input is invalid until the input is correct (as instructed). –  May 10 '14 at 18:40
  • 4
    @fay But if the input is invalid too many times, you will get a StackOverflowError. Recursion is more elegant, but in my eyes, usually more of a problem than a regular loop (due to stacks). – Michael Yaworski May 10 '14 at 18:51
  • 1
    That's an interesting and good point, then. I hadn't considered that error. Though, can the same effect can be achieved through `while(true)` calling the same method? If so, I wouldn't say this supports any difference between recursion, good to know as it is. –  May 10 '14 at 18:57
  • @mikeyaworski True, but that's a resource constraint rather than a conceptual error on fay's part, and in some languages could be optimized out by the compiler since it sounds like it's a tail recursion. – pjs May 10 '14 at 19:02
  • @pjs Conceptually, it may not be a problem. But practically, it is. Practicality is what matters. We're working with Java right now and it's unlikely that stacks *wont* be a problem. – Michael Yaworski May 10 '14 at 19:12
  • 1
    @fay `while(true)` is an infinite loop. Unless you have a `break` statement, I don't see the point in it, unless you're trying to crash your program lol. My point is, if you call the same method (that's recursion), it will sometimes give you a [StackOverflowError](http://docs.oracle.com/javase/7/docs/api/java/lang/StackOverflowError.html), but if you use a `while` or `for` loop, it will not. The problem simply doesn't exist with a regular loop. Maybe I misunderstood you, but my answer to you is no. – Michael Yaworski May 10 '14 at 19:15
  • @mikeyaworski I'd disagree that stack overflow is likely to be a realistic problem for human interaction in a tic-tac-toe game. I can't imagine somebody neither entering correct input nor giving up and aborting the program for 1000 interactions. – pjs May 10 '14 at 19:28
  • @pjs Oh you're right on that one lol. I forgot what kind of program we're working with. I was more so thinking of calculators and other types of mathematical applications, perhaps Project Euler problems. – Michael Yaworski May 10 '14 at 19:40
  • 4
    This to me honestly seems like probably the real reason the professor took marks off =) He may not have explained it very well, but it's a valid complaint to say that you were using it in a way that would be considered very poor style if not outright flawed in more serious code. – Commander Coriander Salamander May 11 '14 at 00:20
  • @remudada: TCO. Look it up. – nomen May 11 '14 at 19:47
  • @nomen, care to elaborate. I did not understand. . . – remudada May 12 '14 at 05:04
3

Recursion is a programming concept, a feature (like iteration), and a practice. As you can see from the link, there's a large domain of research dedicated to the subject. Perhaps we don't need to go that deep in the topic to understand these points.

Recursion as a feature

In plain terms, Java supports it implicitly, because it allows a method (which is basically a special function) to have "knowledge" of itself and of others methods composing the class it belongs to. Consider a language where this is not the case: you would be able to write the body of that method a, but you wouldn't be able to include a call to a within it. The only solution would be to use iteration to obtain the same result. In such a language, you would have to make a distinction between functions aware of their own existence (by using a specific syntax token), and those who don't! Actually, a whole group of languages do make that distinction (see the Lisp and ML families for instance). Interestingly, Perl does even allow anonymous functions (so called lambdas) to call themselves recursively (again, with a dedicated syntax).

no recursion?

For languages which don't even support the possibility of recursion, there is often another solution, in the form of the Fixed-point combinator, but it still requires the language to support functions as so called first class objects (i.e. objects which may be manipulated within the language itself).

Recursion as a practice

Having that feature available in a language doesn't necessary mean that it is idiomatic. In Java 8, lambda expressions have been included, so it might become easier to adopt a functional approach to programming. However, there are practical considerations:

  • the syntax is still not very recursion friendly
  • compilers may not be able to detect that practice and optimize it

The bottom line

Luckily (or more accurately, for ease of use), Java does let methods be aware of themselves by default, and thus support recursion, so this isn't really a practical problem, but it still remain a theoretical one, and I suppose that your teacher wanted to address it specifically. Besides, in the light of the recent evolution of the language, it might turn into something important in the future.

Community
  • 1
  • 1
didierc
  • 14,572
  • 3
  • 32
  • 52