I'd have to say that there is one(and only one) issue in Haskell
that bugs me to the point where I start to think it's a design
flaw:
It's much easier to type things over generally than it is to type
things correctly.
Say we have a
>data BadFoo =
>
BadBar{
> badFoo::Int} |
> BadFrog{
>
badFrog::String,
> badChicken::Int}
This is fine, until we
want to write a function that acts on Frogs but not on Bars. The best we
can do is throw a runtime error when passed a Bar and not a
Foo:
>deBadFrog :: BadFoo -> String
>deBadFrog (BadFrog s
_) = s
>deBadFrog BadBar{} = error "Error:
This is not a frog."
We cannot type our function such that it only
takes Frogs and not Bars. This makes what should be a trivial compile
time error into a nasty runtime one :(
The only solution I have found
to this is a rather ugly one:
>data Foo = Bar BarT | Frog
FrogT
If I then create new types for each data
constructor.
>data FrogT = FrogT{
> frog::String,
>
chicken::Int}
>data BarT = BarT{
> foo :: Int}
Then I
can type deFrog correctly.
>deFrog :: FrogT ->
String
>deFrog (FrogT s _) = s
But it costs us much more code to
do it correctly. I've never seen it done
correctly. It's just too ugly to do it right :/ and for no good
reason. It seems to me, that it was a design mistake to make data
constructors and types as different entities and this is not something I know
how to fix easily with the number of lines of Haskell code in existence today
:/
>main = do
> frog <- return (Frog (FrogT "Frog"
42))
> print $
> case frog of
> (Frog
myFrog) -> deFrog myFrog
> badFrog <- return (BadBar 4)
>
print $
> case badFrog of
> (notAFrog@BadBar{})
-> deBadFrog notAFrog
The ease with which we make bad design choices
and write bad code(in this particular case) is tragically astounding.
Any suggestions on how the right way could be written more cleanly are
very welcome!
Timothy Hobbs