But the big advantage of Haskell's type system comes with compound types. Haskell's (algebraic) type system allows you to much more precisely design the types of your application. Just for starters, Haskell doesn't assume that any structure could always be null. Tony Hoare, who invented null, has called it his Billion Dollar Mistake.
When you declare a Haskell type, you decide whether it can be null or not; if you don't say it can, then it can't. So for example, in Haskell you might declare a color as comprising red, green, and blue components, each a single byte:
Answer by Peter Schachte:
Too many examples to know where begin. For one thing, any program that uses any types other than primitive types.No, wait. Primitive types, too. For example, in C or Java or pretty much any strongly typed language, you can triple an int or short or double or float variable by writing "*3" after it. But if you want to write a function "triple" that just takes an input and does "*3" on it, you can make it work on ints or shorts of doubles or floats, but only on one of those. If you write the same thing in Haskell, it will work on all those types.And if, for example, you want your C or Java or whatever code to work with unbounded precision integers or with rational numbers, or any kind of numbers that are not primitive types, you can't just write "*3" to triple a number. You'll have to define functions like "unbounded_times" and "rational_times". In Haskell, you can just write "*3". And you can also use that same "triple" function you defined, without any changes, on your unbounded precision integer and rational number types. This makes it a lot easier to change your mind about the types you use in your Haskell program than in other languages.But the big advantage of Haskell's type system comes with compound types. Haskell's (algebraic) type system allows you to much more precisely design the types of your application. Just for starters, Haskell doesn't assume that any structure could always be null. Tony Hoare, who invented null, has called it hisWhen you declare a Haskell type, you decide whether it can be null or not; if you don't say it can, then it can't. So for example, in Haskell you might declare a color as comprising red, green, and blue components, each a single byte:data Color = Color Int8 Int8 Int8Now when your function is declared to receive a Color as input, it doesn't need to check that it's not null; the type system will ensure that. If you want a particular function to accept an optional Color argument, you declare the type to beMaybe Color
, and in this case, the type system won't let you use the color until you make sure it's there. No null pointer exceptions.The Maybe type constructor is a much more uniform way to handle optional values than using null. First, it's only applied where explicitly specified, so you don't need to check where you don't say a value is optional. And second, it can be applied to any type.Maybe Int
for example says you might have an int, or you might not. Try passing null where an int is expected in C code, and you'll just get 0. SimilarlyMaybe [Int]
says you may have a list of ints or you may not. An empty list is different from no list at all. Again, C or Java code would not so easily make this distinction.Algebraic types also more easily and uniformly capture the possibile values of a type. For example, we could define a playing card type asdata Suit = Clubs | Diamonds | Hearts | Spades data Rank = R2 | R3 | R4 | R5 | R6 | R7 | R8 | R9 | R10 | Jack | Queen |King | Ace data Card = Card Rank SuitOf course, this is done just as easily with two enums and a struct in C. But if you later decide to allow red and black jokers in your deck, too, the C type becomes much more complicated. In Haskell, it just requires adding a new JokerKind type and extending the Card type to permit jokers:data JokerKind = RedJoker | BlackJoker data Card = NormalCard Rank Suit | JokerCard JokerKindWhen you make a change like this, the Haskell compiler also helps you find and fix all the places in your code that need to be modified to account for the change to the Card type. And, as a nice little bonus, the compiler will automatically generate code to compare two of any type you define for equality, or for less or greater, or to convert a value to or from a string, or several other useful operations, if you ask it to.Examples like this can't really capture the benefits of an algebraic type system. The real benefit is that it encourages you to think about your data at a more abstract level, at the level of a data model, rather than at the level of bits and bytes. You spend less time trying to contort and torture your data to fit the way that your language wants to think about it, and instead think about what the possible values are, and what values are not possible or not meaningful, and then simply write that directly in Haskell. And that means when you write your code, you don't have to worry about testing for or avoiding nonsensical values; you just need to handle the possibilities. It also means that the compiler finds more of your bugs for you. It's common joke among Haskellers that when your program successfully compiles, it's usually correct; of course that's an exaggeration, but a surprisingly small one.There are other nice aspects of Haskell's type system, and plenty other nice Haskell features beyond the type system, but I hope this gives you an idea of why Haskell's type system is so much nicer than the type systems of common procedural or OO languages.