Reputation: 51
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
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