Embrace Ambiguity with Haskell’s Types
Chances are, if you’ve spent time working in a dynamically-typed programming language, you’ve enjoyed being able to run a program that was inconsistently typed. For example, maybe you started changing the type of something in a large codebase, and you wanted to debug part of the program without updating all of the references to that type in the entire program. You’ve probably also come across scenarios where you’ve needed to write or extend a function, and you didn’t know exactly what type was needed to make things fit together.
Let’s say that you are implementing a feature in Yesod and it requires you to write a handler. You change a type such that the module containing the HTTP handler (in addition to everything else the feature depends on) is typed correctly, yet there are modules which depend on the type that are now inconsistent with the changed type. You may want to play around with handler before deciding to commit to the change.
Programming is often done under imperfect conditions, where things are broken or you’re uncertain about how to extend a system without introducing bugs. It turns out that Haskell is designed specifically to help you to proceed with confidence in exactly these kinds of real-world scenarios. Since GHC 7.6 was released, features have been added to allow you to defer type errors to runtime for debugging, as well as to leverage the type inference engine to help you learn more about your program’s types.
The chart below summarizes the compiler options that allow you to leverage these features of GHC:
||Defers all compile-time type errors to run time. Allows you to run a program for debugging purposes that isn’t completely, consistently typed. Type errors will appear as warnings, but you will be able to compile and run your program.|
||Enabled by default; allows you to get extra information from the type checker by using a placeholder prefixed by ‘_’. Useful when you don’t know the type of a term that you need to write, particularly in a nested expression (for top-level expressions you can just ask GHCi about the inferred type of the function). When used with -fdefer-type-errors, allows the program to compile, and the placeholder is treated as
||Allows you to specify some types in a signature, and leave placeholders in others so that the types can be filled in by type inference. You can think of this feature as a counterpart to typed holes, but at the level of the type signature. Used when you want to have a signature that is partially inferred, and partially explicitly written. This feature is new in GHC 7.10.|
If you previously avoided Haskell because you thought that it assumed you work in a perfect world where your entire program is consistent and you knew all of the types of things in your program, it may be time to take another look. With features like deferred type errors, typed holes, and partial type signatures, Haskell is specifically designed to help you comfortably find your way while working on messy, broken, real-world systems.
If you’d like to see these features in action, we’ve put together a GitHub repo with examples of these different compile-time options as they’re used with GHC 7.10.
I wrote this blog post after a discussion with Haskell developers at Stack Builders related to how Haskell helps with incremental development and program change. Many thanks to Juan Pedro Villa and Eric Jones for comments on the initial draft of this post.