28

It seems that all new programming languages or at least the ones that became popular use type inference. Even Javascript got types and type inference though various implementations (Acscript, typescript etc). It looks great to me but I'm wondering if there are any trade-offs or why let's say Java or the old good languages don't have type inference

  • When declaring a variable in Go without specifying its type (using var without a type or the := syntax), the variable's type is inferred from the value on the right hand side.
  • D allows writing large code fragments without redundantly specifying types, like dynamic languages do. On the other hand, static inference deduces types and other code properties, giving the best of both the static and the dynamic worlds.
  • The type inference engine in Rust is pretty smart. It does more than looking at the type of the r-value during an initialization. It also looks how the variable is used afterwards to infer its type.
  • Swift uses type inference to work out the appropriate type. Type inference enables a compiler to deduce the type of a particular expression automatically when it compiles your code, simply by examining the values you provide.
  • 3
    In C#, the general guidelines says to not always use var because it can sometimes hurt the readability. – Mephy Feb 3 '15 at 2:49
  • 5
    "...or why let's say Java or the old good languages don't have type inference" The reasons are probably historical; ML appeared 1 year after C according to Wikipedia and it had type inference. Java was trying to appeal to C++ developers. C++ started as an extension of C, and C's primary concerns were being a portable wrapper over assembly and being easy to compile. For whatever it's worth, I've read that subtyping makes type inference undecidable in the general case as well. – Doval Feb 3 '15 at 3:14
  • 3
    @Doval Scala seems to do a pretty good job at inferring types, for a language that supports subtype inheritance. It's not as good as any of the ML-family languages, but it's probably as good as you could ask for, given the language's design. – KChaloux Feb 3 '15 at 16:15
  • 12
    It’s worthwhile to draw a distinction between type deduction (monodirectional, like C# var and C++ auto) and type inference (bidirectional, like Haskell let). In the former case, the type of a name may be inferred from its initialiser only—its uses must follow the type of the name. In the latter case, the type of a name may be inferred from its uses also—which is useful in that you can write simply [] for an empty sequence, regardless of the element type, or newEmptyMVar for a new null mutable reference, regardless of the referent type. – Jon Purdy Feb 5 '15 at 8:55
  • Type inference can be incredibly complicated to implement, especially efficiently. People don't want their compilation complexity to suffer exponential blowup from more complex expressions. Interesting read: cocoawithlove.com/blog/2016/07/12/type-checker-issues.html – Alexander Sep 20 '17 at 4:08
42

Haskell's type system is fully inferrable (leaving aside polymorphic recursion, certain language extensions, and the dreaded monomorphism restriction), yet programmers still frequently provide type annotations in the source code even when they don't need to. Why?

  1. Type annotations serve as documentation. This is especially true with types as expressive as Haskell's. Given a function's name and its type you can usually have a pretty good guess at what the function does, especially when the function is parametrically polymorphic.
  2. Type annotations can drive development. Writing a type signature before you write the body of a function feels kind-of like test-driven development. In my experience, once you make a Haskell function compile it usually works first time. (Of course, this does not obviate the need for automated tests!)
  3. Explicit types can help you check your assumptions. When I'm trying to understand some code that already works, I frequently pepper it with what I believe to be the correct type annotations. If the code still compiles I know I've understood it. If it doesn't, I read the error message.
  4. Type signatures let you specialise polymorphic functions. Very occasionally, an API is more expressive or useful if certain functions are not polymorphic. The compiler won't complain if you give a function a less general type than would be inferred. The classic example is map :: (a -> b) -> [a] -> [b]. Its more general form (fmap :: Functor f => (a -> b) -> f a -> f b) applies to all Functors, not just lists. But it was felt that map would be easier to understand for beginners, so it lives on alongside its bigger brother.

On the whole, the downsides of a statically-typed-but-inferrable system are much the same as the downsides of static typing in general, a well-worn discussion on this site and others (Googling "static typing disadvantages" will get you hundreds of pages of flame-wars). Of course, some of said disadvantages are ameliorated by the smaller quantity of type annotations in an inferrable system. Plus, type inference has its own advantages: hole-driven development wouldn't be possible without type inference.

Java* proves that a language requiring too many type annotations gets annoying, but with too few you lose out on the advantages I described above. Languages with opt-out type inference strike an agreeable balance between the two extremes.

*Even Java, that great scapegoat, performs a certain amount of local type inference. In a statement like Map<String, Integer> = new HashMap<>();, you don't have to specify the generic type of the constructor. On the other hand, ML-style languages are typically globally inferrable.

  • 2
    But you're not a beginner ;) You have to have an understanding of type classes and kinds before you really "get" Functor. Lists and map are more likely to be familiar to non-seasoned Haskellers. – Benjamin Hodgson Feb 3 '15 at 8:48
  • 1
    Would you consider C#'s var to be an example of type inference? – Benjamin Hodgson Feb 3 '15 at 22:17
  • 1
    Yes, C#'s var is a proper example. – Doval Feb 3 '15 at 22:19
  • 1
    The site is already flagging this discussion as too lengthy so this'll be my last reply. In the former the compiler must determine the type of x; in the latter there is no type to determine, all types are known and you just have to check that the expression makes sense. The difference becomes more important as you move past trivial examples and x is used in multiple places; the compiler then has to cross-check the locations where x is used to determine 1) is it possible to assign x a type such that the code will type check? 2) If it is, what's the most general type we can assign it? – Doval Feb 3 '15 at 22:25
  • 1
    Note that the new HashMap<>(); syntax was only added in Java 7, and the lambdas in Java 8 allow for quite a lot of "real" type inference. – Michael Borgwardt Feb 5 '15 at 8:36
8

In C#, type inference occurs at compile-time, so the runtime cost is zero.

As a matter of style, var is used for situations where it is either inconvenient or unnecessary to manually specify the type. Linq is one such situation. Another is:

var s = new SomeReallyLongTypeNameWith<Several, Type, Parameters>(andFormal, parameters);

without which you would be repeating your really long type name (and the type parameters) rather than simply saying var.

Use the actual name of the type when being explicit improves code clarity.

There are some situations where type inference cannot be used, such as member variable declarations whose values are set at construction time, or where you really want intellisense to work properly (Hackerrank's IDE won't intellisense the variable's members unless you declare the type explicitly).

  • 15
    "In C#, type inference occurs at compile-time, so the runtime cost is zero." Type inference occurs at compile-time by definition, so that's the case in every language. – Doval Feb 3 '15 at 3:05
  • So much the better. – Robert Harvey Feb 3 '15 at 3:06
  • 1
    @Doval it is possible to perform type inference during JIT compilation, which does clearly have a runtime cost. – Jules Feb 3 '15 at 9:37
  • 5
    @Jules If you've gotten as far as running the program then it's already been type checked; there's nothing left to infer. What you're talking about isn't typically called type inference, though I'm not sure what the proper term is. – Doval Feb 3 '15 at 12:55
7

Good question!

  1. Since the type is not explicitly annotated, it can at times make the code harder to read - leading to more bugs. Properly used it of course makes the code cleaner and more readable. If you're a pessimist and think that most programmers are bad (or work where most programmers are bad), this will be a net loss.
  2. While the type inference algorithm is relatively simple, it is not free. This sort of thing increases compile time slightly.
  3. Since the type is not explicitly annotated, your IDE can't guess as well what you're trying to do, harming autocomplete and similar helpers during the declaration process.
  4. Combined with function overloads, you can occasionally get into situations where the type inference algorithm can't decide which path to take, leading to uglier casting style annotations. (This happens quite a bit with C#'s anonymous function syntax for example).

And there are more esoteric languages that cannot do their weirdness without explicit type annotation. So far, there are none that I know of that are common/popular/promising enough to mention except in passing.

  • 1
    autocomplete doesn't give a shit about type inference. It doesn't matter how the type was decided, only what the type has. And the problems with C#'s lambdas are nothing to do with inference, they are because the lambdas are polymorphic but the type system can't cope with it- which has nothing to do with inference. – DeadMG Feb 3 '15 at 9:26
  • 1
    @deadmg - sure, once the variable is declared it has no problem, but while you're typing in the variable declaration, it can't narrow the options of the right hand side to the declared type since there is no declared type. – Telastyn Feb 3 '15 at 12:44
  • @deadmg - as for the lambdas, I'm not super clear on what you mean, but I'm pretty sure it's not entirely correct based on my understanding of how things work. Even something as simple as var foo = x => x; fails because the language needs to infer x here and has nothing to go on. When the lambdas are built, they're built as explicitly typed delegates Func<int, int> foo = x => x is built into CIL as Func<int, int> foo = new Func<int, int>(x=>x); where the lambda is built as a generated, explicitly typed function. To me, inferring the type of x is part and parcel of type inference... – Telastyn Feb 3 '15 at 14:15
  • 3
    @Telastyn It's not true that the language has nothing to go on. All the information needed to type the expression x => x is contained inside the lambda itself--it doesn't refer to any variables from the surrounding scope. Any sane functional language would correctly infer that its type is "for all types a, a -> a". I think what DeadMG is trying to say is that C#'s type system lacks the principal type property which means that it's always possible to figure out the most general type for any expression. It's a very easy property to destroy. – Doval Feb 3 '15 at 14:30
  • @Doval - enh... there's no such thing as generic function objects in C#, which I think is subtly different than what you're both talking about even if it leads to the same trouble. – Telastyn Feb 3 '15 at 14:35
4

It looks great to me but I'm wondering if there are any trade-offs or why let's say Java or the old good languages don't have type inference

Java happens to be the exception rather than the rule here. Even C++ (which I beleive qualifies as a "good old language" :) ) supports type inference with the auto keyword since the C++11 standard. It not only works with variable declaration, but also as function return type, which is especially handy with some complex template functions.

Implicit typing and type inference have many good use cases, and there are also some use cases where you really shouldn't do it. This is sometimes matter of taste, and also subject of debate.

But that there are undoubtedly good use cases, is by itself a legitimate reason for any language to implement it. It is also not a hard to implement feature, no runtime penalty, and does not affect compile time significantly.

I do not see a real drawback in giving an opportunity to the developer to use type inference.

Some answerer reasond how explicit typing is good sometimes, and they are certainly right. But not supporting implicit typeing would mean that a language enforces explicit typing all the time.

So the real draw back is when a language does not support implicit typeing, because with this it states, that there is no legitimate reason for the developer to use it, which is not true.

1

The primary distinction between a Hindley-Milner type inference system and Go-style type inference is the direction of information flow. In HM, type information flows forwards and backwards via unification; in Go, type information flows forwards only: it computes forwards substitutions only.

HM type inference is a splendid innovation that works well with polymorphically typed functional languages, but Go's authors would probably argue that it tries to do too much:

  1. The fact that information flows both forwards and backwards means that HM type inference is a very nonlocal affair; in the absence of type annotations, every line of code in your program could be contributing to the typing of a single line of code. If you only have forward substitution, you know that the source of a type error must be in the code that precedes your error; not the code that comes after.

  2. With HM type inference, you tend to think in constraints: when you use a type, you constraint what possible types it can have. At the end of the day, there may be some type variables which are left totally unconstrained. The insight of HM type inference is that, those types really don't matter, and so they are made into polymorphic variables. However, this extra polymorphism can be undesirable for a number of reasons. First, as some people have pointed out, this extra polymorphism could be undesirable: HM concluding a bogus, polymorphic type for some bogus code, and lead to strange errors later. Second, when a type is left polymorphic, this can have consequences for runtime behavior. For example, an overly polymorphic intermediate result is the reason why 'show . read' is considered ambiguous in Haskell; as another example, polymorphic values must be evaluated multiple times for each type they are evaluated at, motivating the monomorphism restriction.

  • 1
    Unfortunately, this looks like a good answer to a different question. OP is asking about "Go-style type inference" versus languages that don't have any type inference at all, not "Go-style" versus HM. – Ixrec Mar 22 '16 at 9:13
-2

It hurts readability.

Compare

var stuff = someComplexFunction()

vs

List<BusinessObject> stuff = someComplexFunction()

It's especially a problem when reading outside an IDE like on github.

  • 3
    this doesn't seem to offer anything substantial over points made and explained in prior 6 answers – gnat Sep 20 '17 at 4:41
  • If you use proper name instead of "complex unreadable", then var can be used without hurting readability. var objects = GetBusinessObjects(); – Fabio Sep 24 '17 at 15:15
  • Ok but then you're leaking the type system into the variable names. It's like hungarian notation. After refactorings you're more likely to end up with names that don't match the code. In general, functional style pushes you to lose the natural namespacing advantages that classes provide. So you end up with more squareArea() instead of square.area(). – miguel Sep 25 '17 at 1:50

protected by gnat Sep 20 '17 at 4:38

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.