Practical Benefits of the Haskell Type System
mechanical-elephant.com
mechanical-elephant.com
Is it just me, or is there a huge hole in Haskell's type systems around dictionaries and tuples?
If I understand correctly, if you want to do something like a C/C++ struct (a collection of data fields, not a class with some behavior), then in Haskell, you either make it a dictionary with string keys, or you make it a tuple. But if you pass either of those to a function, don't you lose type safety?
Let's say that I have a structure that represents a person. It's got a first name (string), a last name (string), and an age (non-negative integer). I create a tuple (first, last, age), or I create a dictionary ("first" : first, "last" : last, "age" : age). (The syntax there is lousy, I don't know how Haskell has you assemble a dictionary. It's intended to be read as ("key" : value) entries.)
My function can require that it gets passed a tuple. I suspect that it can require that it gets passed a tuple with three items. But can it require that it gets passed a tuple that is (string, string, integer)? Worse, can it require that it gets passed a tuple that is (first, last, age) and not (city, state, zip code)?
Dictionaries are even worse. The function may be able to require that it gets passed a dictionary with three entries. But can it guarantee that it gets passed a dictionary with a "first" key, and that the value of that key is a string?
So it seems to me that C/C++ in this area can give you stronger type safety than Haskell, because a function can guarantee that it got passed a Person struct, which has a string first, and a string last, and an int age, and that it did not get called with anything else.
Or am I all wet, and only an idiot would try to do structs that way in Haskell?
You could also easily define more specific types like for example: type Age = Int, which should prevent somebody from creating a person where they use a zip code for that person's age.
data Person = Person
{ firstName :: String
, lastName :: String
, age :: Int
}
The compiler automatically creates the following functions for you. Person :: String -> String -> Int -> Person
firstName :: Person -> String
lastName :: Person -> String
age :: Person -> Int
So you get all the expected type safety. The nice thing is that since this syntax is very light, you end up creating all kinds of small types here and there for better type safety. Whenever I find myself using tuples of more than 3 or so items, I'll frequently create a new type for it. This serves as better documentation and makes the code easier to refactor.The main type system weakness of tuples, is that their is no way to write a generic tuple function. For example, if you want to write a function to return the first element of a tuple, you need to encode into the type of that function the number of elements in the tuple.
In your dictionary example, use the "data" keyword to define a new type. Unless age is the same type as first and last, you cannot (easily) even create the dictionary you describe.