I'm starting to learn Haskell. I'm very new to it, and I am just reading through a couple of the online books to get my head around its basic constructs.
One of the 'memes' that people familiar with it have often talked about, is the whole "if it compiles, it will work*" thing – which I think is related to the strength of the type system.
I'm trying to understand why exactly Haskell is better than other statically typed languages in this regard.
Put another way, I assume in Java, you could do something heinous like bury
ArrayList<String>()
to contain something that really should be ArrayList<Animal>()
. The heinous thing here is is that your string
contains elephant, giraffe
, etc, and if someone puts in Mercedes
– your compiler won't help you.
If I did do ArrayList<Animal>()
then, at some later point in time, if I decide my program isn't really about animals, it's about vehicles, then I can change, say, a function that produces ArrayList<Animal>
to produce ArrayList<Vehicle>
and my IDE should tell me everywhere there is a compilation break.
My assumption is that this is what people mean by a strong type system, but it's not obvious to me why Haskell's is better. Put another way, you can write good or bad Java, I assume you can do the same in Haskell (i.e stuff things into strings/ints that should really be first-class data types).
I suspect I'm missing something important/basic.
I would very happy to be shown the error of my ways!
Best Answer
Here's an unordered list of type system features available in Haskell and either unavailable or less nice in Java (to my knowledge, which is admittedly weak w.r.t. Java)
Eq
uality might be auto-derived for a user-defined type by a Haskell compiler. Essentially the way that it does this is walk over the common, simple structure underlying any user-defined type and match it up between values—a very natural form of structural equality.data Bt a = Here a | There (Bt (a, a))
. Think carefully about the valid values ofBt a
and notice how that type works. It's tricky!IO
. Java probably actually has a nicer abstract type story, to be honest, but I don't think until Interfaces became more popular was that genuinely true.mtl
effect typing system, generalized functor fixpoints. The list goes on and on. There are a lot of things which are best expressed at higher kinds and relatively few type systems even allow the user to talk about these things.(+)
things together? Oh,Integer
, ok! Let's inline the right code now!". At more complex systems you might be establishing more interesting constraints.mtl
library is based on this idea.(forall a. f a -> g a)
. In straight HM you can write a function at this type, but with higher-rank types you demand such a function as an argument like so:mapFree :: (forall a . f a -> g a) -> Free f -> Free g
. Notice that thea
variable is bound only within the argument. This means that the definer of the functionmapFree
gets to decide whata
is instantiated at when they use it, not the user ofmapFree
.Kind indexed types and type promotion. I'm getting really exotic at this point, but these have practical use from time to time. If you'd like to write a type of handles which are either open or closed then you can do that very nicely. Notice in the following snippet that
State
is a very simple algebraic type which had its values promoted into the type-level as well. Then, subsequently, we can talk about type constructors likeHandle
as taking arguments at specific kinds likeState
. It's confusing to understand all the details, but also so very right.Runtime type representations that work. Java is notorious for having type erasure and having that feature rain on some people's parades. Type erasure is the right way to go, however, as if you have a function
getRepr :: a -> TypeRepr
then you at the very least violate parametricity. What's worse is that if that's a user-generated function which is used to trigger unsafe coercions at runtime... then you've got a massive safety concern. Haskell'sTypeable
system allows the creation of a safecoerce :: (Typeable a, Typeable b) => a -> Maybe b
. This system relies onTypeable
being implemented in the compiler (and not userland) and also could not be given such nice semantics without Haskell's typeclass mechanism and the laws it is guaranteed to follow.More than just these however the value of Haskell's type system also relates to how the types describe the language. Here are a few features of Haskell which drive value through the type system.
IO a
to represent side-effecting computations which result in values of typea
. This is the foundation of a very nice effect system embedded inside of a pure language.null
. Everyone knows thatnull
is the billion dollar mistake of modern programming languages. Algebraic types, in particular the ability to just append a "does not exist" state onto types you have by transforming a typeA
into the typeMaybe A
, completely mitigate the problem ofnull
.Bt a
type from before and try to write a function to compute its size:size :: Bt a -> Int
. It'll look a bit likesize (Here a) = 1
andsize (There bt) = 2 * size bt
. Operationally that isn't too complex, but notice that the recursive call tosize
in the last equation occurs at a different type, yet the overall definition has a nice generalized typesize :: Bt a -> Int
. Note that this is a feature which breaks total inference, but if you provide a type signature then Haskell will allow it.I could keep going, but this list ought to get you started-and-then-some.