zarazek
zarazek

Reputation: 51

Can I write such polymorphic function? What language extensions do I need?

I tried to write a couple of functions that parse numbers properly, with all the checking (of signature String -> Maybe a for some collection of a). I have written function that parses unbounded Integer (maybeReadInteger), and then I wanted to write polymorphic function that parses all bounded integer types: just wrap maybeReadInteger in some range checking. maybeReadNum is attempt to write such function, but it doesn't typecheck. Can it be written like this? What language extensions (if any) I have to turn on?

splitIntoSignAndDigits str = case str of 
    '-':rest -> (-1, rest)
    '+':rest -> ( 1, rest)
    _        -> ( 1, str )

maybeReadUnsignedInteger []         = Nothing
maybeReadUnsignedInteger str@(x:xs) = go 0 str
    where go n str = case str of
              []                            -> Just n
              (x:xs) | '0' <= x && x <= '9' -> go (10 * n + digit) xs
                     | otherwise            -> Nothing
                  where digit = toInteger (ord x - ord '0')

maybeReadInteger str = fmap (sign*) (maybeReadUnsignedInteger str')
    where (sign, str') = splitIntoSignAndDigits str

maybeReadNum :: (Integral a, Bounded a) => String -> Maybe a
maybeReadNum = fmap fromInteger . 
               mfilter (\n -> n >= toInteger (minBound :: a) && 
                              n <= toInteger (maxBound :: a)) .
               maybeReadInteger

Monomorphic function like this:

maybeReadInt :: String -> Maybe Int
maybeReadInt = fmap fromInteger . 
               mfilter (\n -> n >= toInteger (minBound :: Int) && 
                              n <= toInteger (maxBound :: Int)) .
               maybeReadInteger

works OK.

Upvotes: 3

Views: 151

Answers (1)

bennofs
bennofs

Reputation: 11963

The problem is that the a in your signature for minBound is not the same as the a in the signature of maybeReadNum. You can fix this by turning on ScopedTypeVariables:

{-# LANGUAGE ScopedTypeVariables #-}

-- We need to use forall explicitly, otherwise ScopedTypeVariables doesn't take effect.
maybeReadNum :: forall a. (Integral a, Bounded a) => String -> Maybe a
maybeReadNum = fmap fromInteger . 
               mfilter (\n -> n >= toInteger (minBound :: a) && 
                              n <= toInteger (maxBound :: a)) .
               maybeReadInteger

An alternative is to use a helper like asTypeOf:

maybeReadNum :: (Integral a, Bounded a) => String -> Maybe a
maybeReadNum = fmap ((`asTypeOf` minBound') . fromInteger) . 
               mfilter (\n -> n >= toInteger minBound' && 
                              n <= toInteger maxBound') .    
               maybeReadInteger
  where minBound' = minBound
        maxBound' = maxBound `asTypeOf` minBound'

asTypeOf is defined as follows:

asTypeOf :: a -> a -> a
asTypeOf = const

It is just const with a more restricted type siganture. This can be used to assert the the the types of two expressions should be the same, which helps the compiler to infer the right types.

In the above code, asTypeOf is used to assert that minBound' should have the same type as the result of fromInteger, which in turn must equal to the a from the signature of maybeReadNum. From this, the compiler infers that minBound' is of type a. Then maxBound' should also have the same type, so it also gets the type a. This is a bit more difficult, and it's probably easier and better to just use ScopedTypeVariables, it might however be a method to retain portable across compilers (in case you aren't already using some extensions).

Upvotes: 5

Related Questions