2

I would like the compiler to warn me when it applies tail recursion to one of my functions when I haven't told it to with the annotation. Is this possible?

Motivation: I very rarely write infinite loops as a matter of logic error, but I have done with typos (yes it is possible). Usually recursive infinite loops just tell you what is wrong with a stack overflow exception, but not if they get compiled tail recursively, it just hangs.

Forgetting the new keyword in combination with case classes with default params is a good example that I have foolishly stumbled on twice:

case class A(a: Int, b: Int = 1)

object A {
  def apply(a: Int): A = A(a)

Causes an infinite loop with no SO, but def apply(a: Int): A = new A(a) does not

samthebest
  • 30,803
  • 25
  • 102
  • 142
  • 1
    what about `object A { def b: Any = c; def c: Any = b }`—there is simply no way for the compiler to warn you of every possible mistake you make. Also you may create an infinite loop where the recursive call is _not_ in tail-recursive position. So your proposed warning wouldn't work, either. – 0__ Dec 29 '14 at 22:59
  • 2
    Slightly off topic but if you write unit tests for your code, these kinds of problems wouldn't arise – tddmonkey Dec 29 '14 at 23:25
  • @MrWiggles Not really. 1. the unit tests have to run continually or be frequently manually invoked, 2. even when they hang they don't tell you _what_ is wrong just that _something_ is wrong, 3. SBT (and ones environment) can be slow, unpredictable and hangy under normal circumstances, (my corporate laptop absolutely sucks thanks to morons in security putting crappy unnecessary Symantec on it). – samthebest Dec 30 '14 at 09:07
  • @0__ No, read the question more carefully, that just gives an SO - easy peasy to debug. – samthebest Dec 30 '14 at 09:09
  • 1. Your unit tests should be run very frequently anyway, at the very least when you've just changed a piece of code. 2. If you run your tests frequently enough you can narrow down what's broken to the changeset you've just made. 3. If SBT is as you describe I would stop using it- personally I use gradle for my Scala work – tddmonkey Dec 30 '14 at 10:57
  • @MrWiggles You are basically suggesting migrating my build tool and moving company to get a better laptop. Then even once I do all that I still have to pay close attention to what those test are doing, and when they do hang I still need to remember what it is I was just doing (even when I'm in the middle of a big refactor, or just come back from a meeting). _OR_ I can just use Daenyth's answer which is a single check box in Intellij!! Sorry you're "solution" loses by miles ;) – samthebest Dec 30 '14 at 13:01
  • Actually yes I am suggesting moving your build tool, it's nowhere near as much work as everyone thinks it is and if SBT sucks that bad it is going to pay dividends for many years in the future. If you need to remember what you were doing in the middle of a big refactor I would suggest that your refactoring steps need to be broken down into much smaller steps – tddmonkey Dec 30 '14 at 13:09
  • @MrWiggles Although I agree with your points _in general_ you're missing the point that they do not apply to _this context_ and that the answer given _does_. The problem in the OP is so incredibly tiny that breaking down refactors into line by line operations would be ridiculous. As noted, it's a single typo. As for gradle, yes I should look into it. – samthebest Dec 30 '14 at 13:45
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/67926/discussion-between-mrwiggles-and-samthebest). – tddmonkey Dec 30 '14 at 14:51

2 Answers2

3

I don't think the compiler has this but IntelliJ offers a "No tail recursion annotation" under Inspections > Scala: General

Detects tail recursive methods without @tailrec annotation which verifies that the method will be compiled with tail call optimization.

Daenyth
  • 35,856
  • 13
  • 85
  • 124
0

Avoid declaring function return types when practical: def apply(a: Int) = A(a) would fail to compile, so you would know that something is wrong.

Dima
  • 39,570
  • 6
  • 44
  • 70
  • 3
    I disagree, declaring types in general increases compile time checking. It's much more common a mistake to get types muddled than it is to do accidental tail recursive functions. Getting types muddled is BAU, accidental tail recursion happens once every few months perhaps. – samthebest Dec 30 '14 at 08:52
  • @samthebest: I said "when practical", remember? How exactly is `def apply(a: Int): A = new A(a)` "safer" than `def apply(a: Int) = new A(a)`? What kind of "compile time checking" does it "increase"? Don't speak "in general", be specific. This is what engineering is all about, as opposed to religion. Make *rational* decisions, and base them on real technical merits, not some rigid "rule of thumb" that is really nothing that a though surrogate, meant as a crutch for the weaker minds. – Dima Jan 01 '15 at 15:17
  • I agree that is it OK to omit annotation in certain circumstance, this is indeed such a situation, I disagree that one should "avoid" declaring types as _a rule of thumb_ ;) You are contradicting yourself, "avoid" implies a default stance or inclination beyond that of just the details at hand. Furthermore the question specifically asks for a general way of preventing accidental infinite tail recursion, you must be implying your own solution be applied "in general" or it's utterly pointless as an answer. ... – samthebest Jan 02 '15 at 11:56
  • If I cannot apply your "solution" in general, then you are basically saying "When you know you are making a tail recursive call, do not annotate the type, then the compiler will tell you that you are making a tail recursive call" in such situation I do not need to be told!! What is the point in thinking "hmm, I might cause infinite tail recursion here, I better not annotate type"?? I might as well think "hmm, I might cause infinite tail recursion here, I better check if I am causing infinite tail recursion". – samthebest Jan 02 '15 at 12:02
  • @samthebset no, that's not what I am saying. I am saying "do not use type ascriptions unless you know a good and specific reason you should use them in a particular case". – Dima Jan 02 '15 at 13:31
  • @samthebest, also, I actually insist that if you start thinking about *specific reasons* for ascribing a function return type in each particular case you will (almost) never be able to come up with one. In a strictly typed language, like scala, when a type can be inferred by the compiler, it affords you exactly the same safety as if it was explicitly declared. It does add to readability in some cases, but never to safety. If you would like to come up with an example illustrating a situation of an explicit type declaration adding safety, I'd gladly consider it. – Dima Jan 02 '15 at 13:40
  • Firstly you are offically wrong according to the style guide "All public methods should have explicit type annotations." Type inference is a feature in Scala to avoid boiler plate annotation mainly for local values and private fields, usually the type is clear from a literal or construction. You seem to be missing the point of strongly statically typed languages - that is to get the compiler to check things for you. If you believe a loc to return type `X` and you annotate accordingly then if you were wrong it was in fact type `Y` no bug would arise at runtime thanks to compiler checking. – samthebest Jan 02 '15 at 14:10
  • Secondly you are contradicting yourself "do not use type ascriptions unless you know a good and specific reason you should use them in a particular case" is a rule of thumb, something you are against. Thirdly your confusing "ascriptions" with "annotations", `def method: Int` is an annotation not an ascription. – samthebest Jan 02 '15 at 14:13
  • You mean scala *style* guide, not "scala guide" :) Yes, public methods are advised to have type ascriptions to aid readability. That's not me being "officially wrong", that's exactly what I am saying ;) The point about strict typing and type-inference is that if you believe a function returns `Foo`, and it in fact returns `Bar`, you will get a compile error when you try to use it, *regardless* of whether or not its return type was explicitly declared. That means that explicit declaration does not add to safety. – Dima Jan 02 '15 at 14:13
  • You can call anything a rule of thumb, for the sake of an argument. But since we both know what I am talking about, it makes little sense to continue arguing about terminology. That's the last resort for a poor sophist that has run out of real arguments. And no, it is not an annotation, but an ascription. Annotations are *meta language features*, like `@tailrec` etc. Explicit type declarations aren't it. – Dima Jan 02 '15 at 14:17
  • "you will get a compile error when you try to use it", wrong, types can have the same methods. Confusing `Set`s with `List`s and calling size could result in a bug with no compile error. – samthebest Jan 02 '15 at 14:17
  • If you can use a `Set` same way you would use a `List`, then concrete type does not matter, and, if it was declared, it should have been declared `Iterable` anyhow. – Dima Jan 02 '15 at 14:19
  • You ARE confusing ascriptions with annotations http://docs.scala-lang.org/style/types.html. There ARE examples where annotation saves you (one just given), you ARE contradicting yourself regarding rules of thumb. You ARE wrong on so many levels. – samthebest Jan 02 '15 at 14:20
  • no, you are WRONG ;) I'll concede the terminology point though: they do indeed call it *annotations* in scala, contrary to the regular meaning of the term. And regarding "rules of thumb", I never said I was against them as such, I said I was against *relying on them* religiously as a replacement for one's own common sense and rational reason. – Dima Jan 02 '15 at 14:20
  • `def a = List(1, 2, 3, 4).map(_ % 2) // a.size is 4` vs `def a = Set(1, 2, 3, 4).map(_ % 2) // a.size is 2`. `def a: List[Int] = Set(1, 2, 3, 4).map(_ % 2)` gives type mismatch – samthebest Jan 02 '15 at 14:24
  • Ok, and writing `Set` twice in this case would somehow make it safer in your opinion? – Dima Jan 02 '15 at 14:25
  • `def a: List[Int] = Set(1, 2, 3, 4).map(_ % 2)` gives type mismatch – samthebest Jan 02 '15 at 14:26
  • Yes. `val foo: List = a` gives a type mismatch too. So what? – Dima Jan 02 '15 at 14:27
  • *sigh* Annotating type avoids bugs later down the line. The caller of `a` believes it to return a `List`, calls size, expects 4, gets back 2, bug, bug that would not have happened with type annotation. This last happened to a colleague of mine this week as he believed a `keySet` being returned from a `Map` has a `toList` after it, but it didn't. Had his method been annotated with type, the bug would not have occurred. – samthebest Jan 02 '15 at 14:33
  • If the caller expects it to return a list *and cares about it being a list*, the caller should be using it as a list. Your example is not the typing error, it is a logical error. If the function has a logical error in it ... well, sometimes adding a type annotation helps to discover it, and sometimes, to the contrary, it hides it. In either case, it is just a coincidence. – Dima Jan 02 '15 at 14:38
  • Well done you now understand that type annotations do help discover bugs in code. Now you just need to understand that this happens far more frequently than accidental tail recursive functions, which is the only real case where annotating type hides a bug. If one uses Intellij's ALT+ENTER to do annotate types then it's impossible to get it wrong, but likely that the author may discover the return type is not what they expect. It's not luck one way or the other, it's called **type checking** and it's a good thing. – samthebest Jan 02 '15 at 14:56
  • They discover bugs *as often* as they hide them. No, tail recursion is *not* the only case. @0__ gave you another one in his comment. And that one isn't the only one either. And no, it is not called type checking. And you don't need this condescending tone. I was writing code, probably, before you were even born. Stubbornness and arrogance are bad substitute for cleverness and wits, although could sometimes be mistaken for them, but not for long. – Dima Jan 02 '15 at 15:23
  • OK so infinite recursion, they are hardly different examples. Anyway you conceded public methods should be annotated, since the `apply` method is indeed public you conceded you are wrong on this front too. Your wrong about annotations in general and in this specific case, your wrong about terminology, and let's just say you where barking up the wrong tree, confused, or preaching to the choir about rules of thumb. I think that's fair, let's leave it at that. – samthebest Jan 02 '15 at 15:33
  • Actually, one more thing "Yes, public methods are advised to have type ascriptions to aid readability." Not just to aid readability ... "Type inference may break encapsulation in these cases, because it depends on internal method and class details. Without an explicit type, a change to the internals of a method or val could alter the public API of the class without warning, potentially breaking client code." – samthebest Jan 02 '15 at 15:37
  • I did not "concede" they should be annotated. I said that annotating them may *sometimes* help with readability (*not* type safety). Scala style guide is a guide, not a dogma. Annotating something like `def apply = new A` with ":A" is clearly not helping anything (not even readability), and serves absolutely no purpose whatsoever. If you change public API in a non-backwards compatible way, it will break the client code, yes. Don't do it :) – Dima Jan 02 '15 at 15:38
  • Yes it would be OK in this situation, but the rule of thumb, the GUIDEline is to annotate, NOT to avoid annotations which is what you originally said. Please read the style guide, and I think you need to re-read what you yourself have said. Type annotations prevent muddling up types and getting unexpected results, adding annotations means the **compiler checks the type for you**, when a compiler checks a type this is called **type checking**. It's really not very complicated. – samthebest Jan 02 '15 at 15:48
  • Don't rely on the rules of thumb. Use your own brains. Guides and "rules of thumb" are crutches for those lacking in that department. The rest of us can judge technical decisions on their merits without appealing to religious dogmas as the only argument. You are right, it is not complicated, you just need to *think* about it on your own. No, annotations are not type checking, they are ... well, annotations. – Dima Jan 02 '15 at 16:31
  • Your contradicting yourself again, don't say "avoid adding type annotations" then say "don't use rules of thumb", which is it? Without annotations you don't have type checking on the return type. Adding a type annotation means the compiler will check the return type of the LOCs to ensure it matches what you say it should be. That is type checking. I don't want to pursue this any more I get the feeling your trolling me, you are pretending to argue a point you must know to be wrong because it's so incredibly obvious that you are so incredibly wrong. – samthebest Jan 02 '15 at 17:42
  • I say use your brain. There is no contradiction. Your question was about a concrete, specific, use case, that can be handled in a concrete, specific way, that has no downside. Think about it. It's not hard at all, as long as you get out of habit of using guides as a substitute for intellect. You do sound like an intelligent person. I can't imagine that you are unable to grasp a concept thus obvious, therefore, you must be either extremely misguided or simply a troll. I lean towards the latter possibility. – Dima Jan 02 '15 at 18:46