Search code examples
haskellabstract-data-typequickcheck

QuickCheck Arbitrary instance for abstract data type with smart constructor


I am new to the language, trying to write my first non-trivial program. On the way, I am stuck creating an Arbitrary instance. Yet, I suppose my question to follow points at my general lack of understanding composing several applicative and monadic types. Hence, I hope to gain fundamental insight form the following. Thanks for your help!

I defined an Address type and a smart constructor with validation of the individual fields as follows:

data Address = Address
    { street :: StreetName
    , streetExt :: Maybe StreetName
    , city :: CityName
    , zipCode :: ZipCode
    , country :: CC.CountryCode
    } deriving (Eq, Show)
mkAddress :: Text -> Maybe Text -> Text -> Text -> Text -> Maybe Address
mkAddress aStreet aStreetExt aCity aZipCode aCountry =
    Address <$> mkStreetName aStreet
            <*> Just (aStreetExt >>= mkStreetName)
            <*> mkCityName aCity
            <*> mkZipCode aZipCode
            <*> CC.fromMText aCountry

StreetName, CityName and ZipCode are newtype wrappers for Text with a validating smart constructor that simply limits the maximum length of these fields. The streetExt field is optional. The country code uses Data.ContryCodes.CountryCode.

The overall type is abstract, the defining module only exports the smart constructor but not the data constructor.

I am now trying to create an Arbitrary instance for this type>

instance Arbitrary D.Address where
    arbitrary = do
        maybeAddress <- D.mkAddress <$> arbitrary         -- streetName
                                    <*> return Nothing    -- streetExt
                                    <*> arbitrary         -- city
                                    <*> arbitrary         -- zipCode
                                    <*> elements ["DE", "FR", "AG", "RW"]   -- country
        return fromJust maybeAddress

However, I am stuck with the following type checker error:

• Couldn't match type ‘Maybe a0 -> a0’ with ‘Gen D.Address’
      Expected type: Maybe D.Address -> Gen D.Address
      Actual type: Maybe D.Address -> Maybe a0 -> a0
• The function ‘return’ is applied to two arguments,
      but its type ‘(Maybe a0 -> a0)
                    -> Maybe D.Address -> Maybe a0 -> a0’
      has only three

From this error, I take that there is some issue wrapping the Generator inside the Maybe, or the other way around. But even after several experiments lift and join I can't get it to type check. Thus, I suspect that my mental model is flawed of how the types are wrapped and how the generator monads are returned.

It would be great if someone could point out my mistake. In addition, I would highly appreciate a comment on this type of data modelling with abstract types and smart constructors - is this general recommended practice, or does it lead to problems like the one I am facing?

Thanks very much!


Solution

  • The error message says it (although the subsequent explanation is misleading):

    The function ‘return’ is applied to two arguments
    

    You have

    return fromJust maybeAddress
    

    You want

    return (fromJust maybeAddress)