Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are there default values for record getters in Haskell?

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?

like image 275
Jeff Burdges Avatar asked Dec 16 '22 04:12

Jeff Burdges


2 Answers

(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.

like image 188
Chris Kuklewicz Avatar answered Dec 31 '22 16:12

Chris Kuklewicz


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, ... }
like image 20
Sgeo Avatar answered Dec 31 '22 16:12

Sgeo