Reputation: 4251
There is unsurprisingly a run time exception thrown by the following code :
data Necklace = InvalidNecklace |
Necklace { necklace_id :: Int, meow :: Int, ... }
necklace_id InvalidNecklace
Is there some natural way to define a value for necklace_id
when applied to InvalidNecklace
to take a value rather than throwing an exception?
GHC fails with a multiple declarations error for `necklace_id' if I try the obvious thing :
necklace_id InvalidNecklace = -1
Is there perhaps some pragma that'll tell GHC to replace it's inferred declaration by this declaration?
I could declare InvalidNecklace
to be a record by adding { necklace_id :: Int }
, but afaik I cannot guarantee it always returns -1, and generally makes a horrible mess. I could simply define :
get_necklace_id InvalidNecklace = -1
get_necklace_id x = necklace_id x
but this partially defeats the purpose of records.
I suppose one could create a special invalidNecklace
value by writing :
invalidNecklace = Necklace { necklace_id = -1,
meow = error "meow invalidNecklace accessed", ... }
Are there any disadvantages to this second approach? I certainly lose the ability to make meow
strict or unpacked, but perhaps separate debugging and optimized versions could be maintained. Is there a pragma to locally disable warnings for partially initialized records?
Upvotes: 4
Views: 1145
Reputation: 8153
(UPDATED BELOW)
As you discovered, the getter defined by the Necklace
declaration cannot be further defined. There is no pragma to change this.
The common practice in Haskell would be to use this style:
get_necklace_id :: Necklace -> Maybe Int
get_necklace_id InvalidNecklace = Nothing
get_necklace_id (Necklace x) = Just x
Using a magic return value of "-1" is common style in C-type languages with simpler type systems. But note that Maybe Int
is isomorphic to Necklace
, so it adds little in the simplest case (except access to the large number of common functions for handling Maybe
that may not exist for Necklace
). If you make Necklace
more complicated then get_necklace_id
makes sense.
For larger projects it is possible to have template Haskell or an extra tool automatically create the get_necklace_id
above.
UPDATE: Using fromJust
is not a particularly good idea. To get "reasonable defaults" and "no failure modes" you may compose the get_necklace_id :: Necklace -> Maybe Int
with Data.Maybe.fromMaybe :: a -> Maybe a -> a
(one of the common Maybe handling functions) like this:
from_necklace_id :: Int -> Necklace -> Int
from_necklace_id default = fromMaybe default . get_necklace_id
a_necklace_id :: Necklace -> Int
a_necklace_id = from_necklace_id (-1)
The a_necklace_id
is identical to your function that replaces InvalidNecklace with (-1). Code that needs a different default can use from_necklace_id
.
Upvotes: 8
Reputation: 95
Why can't you have the type Necklace only represent valid Necklaces, and Maybe Necklace for cases where the Necklace might be invalid? Or, avoiding usage of Maybe, something like (note that I'm still not sure what a good naming convention here would be):
data Necklace = InvalidNecklace | NecklaceData NecklaceData
data NecklaceData = NecklaceDataRec { necklace_id :: Int, meow :: Int, ... }
Upvotes: 4