A type signature is not a Java-style signature. A Java-style signature will tell you which parameter is the weight and which is the height only because it mingles the parameter names with the parameter types. Haskell can’t do this as a general rule, because functions are defined using pattern matching and multiple equations, as in:
map :: (a -> b) -> [a] -> [b]
map f (x:xs) = f x : map f xs
map _ [] = []
Here the first parameter is named f
in the first equation and _
(which pretty much means “unnamed”) in the second. The second parameter doesn’t have a name in either equation; in the first parts of it have names (and the programmer will probably think of it as “the xs list”), while in the second it’s a completely literal expression.
And then there’s point-free definitions like:
concat :: [[a]] -> [a]
concat = foldr (++) []
The type signature tells us it takes an parameter which is of type [[a]]
, but no name for this parameter appears anywhere in the system.
Outside an individual equation for a function, the names it uses to refer to its arguments are irrelevant anyway except as documentation. Since the idea of a “canonical name” for a function’s parameter isn’t well defined in Haskell, the place for the information “the first parameter of bmiTell
represents weight while the second represents height” is in documentation, not in the type signature.
I agree absolutely that what a function does should be crystal clear from the “public” information available about it. In Java, that is the function’s name, and the parameter types and names. If (as is common) the user will need more information than that, you add it in the documentation. In Haskell the public information about a function is the function’s name and the parameter types. If the user will need more information than that, you add it in the documentation. Note IDEs for Haskell such as Leksah will easily show you Haddock comments.
Note that the preferred thing to do in a language with a strong and expressive type system like Haskell’s is often to try make as many errors as possible detectable as type errors. Thus, a function like bmiTell
immediately sets off warning signs to me, for the following reasons:
- It takes two parameters of the same type representing different things
- It will do the wrong thing if passed parameters in the wrong order
- The two types don’t have a natural position (as the two
[a]
arguments to++
do)
One thing that is often done to increase type safety is indeed to make newtypes, as in the link that you found. I don’t really think of this as having much to do with named parameter passing, more that it is about making a datatype that explicitly represents height, rather than any other quantity you might want to measure with a number. So I wouldn’t have the newtype values appearing only at the call; I would be using the newtype value wherever I got the height data from as well, and passing it around as height data rather than as a number, so that I get the type-safety (and documentation) benefit everywhere. I would only unwrap the value into a raw number when I need to pass it to something that operates on numbers and not on height (such as the arithmetic operations inside bmiTell
).
Note that this has no runtime overhead; newtypes are represented identically to the data “inside” the newtype wrapper, so the wrap/unwrap operations are no-ops on the underlying representation and are simply removed during compilation. It adds only extra characters in the source code, but those characters are exactly the documentation you’re seeking, with the added benefit of being enforced by the compiler; Java-style signatures tell you which parameter is weight and which is height, but the compiler still won’t be able to tell if you accidentally passed them the wrong way around!