Navigate/Search

Type Inference

Wednesday, November 12th, 2008

Type inference will be in your favorite (mainstream) statically typed language within the year, if it is not already there. There are a few gotchas, but type inference is a tool that can make you code easier to read, thus improving maintainability. It also shortens development time by dozens of seconds.

Type inference is syntax sugar. No more, no less. In a nutshell, type declarations are replaced with a keyword such as auto or var. The compiler then determines the type of the declaration by its context. The best way to explain may be through an example from my job.

Without type inference, there is a lot of duplication:

  1. // I actually had to write some c++ code that was a actually
  2. // worse than this the other day. In my case the namespaces
  3. // had to be preserved and the type names were much longer.
  4. EmailService::TypeNotificationTag WARN =
  5.     EmailService::TypeNotificationTag(
  6.         EmailService::TypeNotificationTag::WARN);
  7. EmailService::DomainNotificationTag SELLER =
  8.     EmailService::DomainNotificationTag(
  9.         EmailService::DomainNotificationTag::SELLER);
  10. . . .
  11. EmailService::sendEmail(emailDetails,
  12.     TagSetBuilder(WARN).add(SELLER).getTagSet());

Notice that the long type name requires the declarations to be wrapped and that the type is mentioned three times per declaration.

With type inference the code is just enough cleaner that it is easier to understand.

  1. // With type inference
  2. // Ed: These normally wouldn’t be wrapped,
  3. // but the ’screen width’ is small
  4. auto WARN = EmailService::TypeNotificationTag(
  5.     EmailService::TypeNotificationTag::WARN);
  6. auto SELLER = EmailService::DomainNotificationTag(
  7.     EmailService::DomainNotificationTag::SELLER);
  8. . . .
  9. EmailService::sendEmail(emailDetails,
  10.      TagSetBuilder(WARN).add(SELLER).getTagSet());

Here is a simpler example that may drive the point home a little more.

  1. // without type inferrence
  2. std::map< std::string,
  3.           std::vector<std::string>
  4.         >::const_iterator iter = items.begin();
  5.  
  6. // with type inference
  7. auto iter = items.begin()

Type inference is not dynamic typing.

How is a duck like a bicycle?
They both have handlebars <pause> except for the duck.

Javascript, Ruby, and Python, all use dynamic typing: The type of the object is determined at run time. Dynamic typing is sometimes refered to as duck typing: if it acts like a duck, use it like you would a duck. While it has a lot of advantages, there can be problems with it. Namely, what happens at run time if you attempt to access an expected method that is not there—even when the rest of the object acts like a duck?

Type inference is not dynamic typing. You actually only need type inference in languages that are statically typed like Java and C++. In type inference, the type is determined at compile time by examining the context of the variable. Good implementations would pick the most abstract type possible; that is, the farthest up the inheritance chain.

Gotchas

There are a few things to know before jumping into throwing autos into your code everywhere. Remember that the compiler needs some sort of context to determine the type of the variable, so type-inferred variable must be assigned to some expression. For example:

  1. auto bad; // no context
  2. auto good = 1;
  3. auto goodItem = new Item<std::string>();
  4. auto goodThingy = goodItem.createThingy(good, alsoGood);

The last two lines in the example leads into a maintainability gotcha. The reader has to know what type is returned by createThingy to know what type goodThingy is; taking valuable time from the code maintainer.

Part of knowing the type is knowing the constness of the variable. This can be especially hard with c++ and const correctness. If there are const and non-const versions of creatThingy, is goodThingy const or isn’t it?

C++ and D developers are not alone in their const troubles. In Java 7, all type-inferred variables will be final.

Language support

Most mainstream static-typed languages have or will have support for type inference.

  • C# already has it
  • D Programming language already has it.
  • Java 7 will have it.
  • C++0x will have it.
  • C++ with TR1/boost can use a macro to simulate it today.
    1. #include <boost/type_traits.hpp>
    2.  
    3. #define AUTO(name, init) \
    4.     boost::remove_reference<typeof(init)>::type name = init
    5. #define AUTO_REF(name, init) \
    6.     typeof(init) name = init
    7. . . .
    8. AUTO(iter, items.begin());
    9.  
    10. // as oppossed to this from the example above
    11. std::map< std::string,
    12.                std::vector<std::string>
    13.         >::const_iterator iter = items.begin();
    14.  
    15. // use AUTO_REF when you need to make a reference
    16. AUTO_REF(ref, *item);

Conclusion

Major languages are getting type inference which is a pleasant syntax sugar to ease readability. When you know the few gotchas, it can save you time and screen space.