我尝试编写了几个正确解析数字的函数,并对String -> Maybe a
的某些集合进行了所有检查(签名a
)。我编写了解析无界Integer
(maybeReadInteger
)的函数,然后我想编写解析所有有界整数类型的多态函数:只需在某些范围检查中包装maybeReadInteger
。 maybeReadNum
试图编写这样的函数,但它没有进行类型检查。可以像这样写吗?我必须打开哪些语言扩展(如果有的话)?
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
这样的单态函数:
maybeReadInt :: String -> Maybe Int
maybeReadInt = fmap fromInteger .
mfilter (\n -> n >= toInteger (minBound :: Int) &&
n <= toInteger (maxBound :: Int)) .
maybeReadInteger
工作正常。
答案 0 :(得分:5)
问题在于a
签名中的minBound
与a
签名中的maybeReadNum
不同。您可以通过启用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
另一种方法是使用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
定义如下:
asTypeOf :: a -> a -> a
asTypeOf = const
仅const
具有更受限制的类型siganture。这可以用来断言两个表达式的类型应该是相同的,这有助于编译器推断出正确的类型。
在上面的代码中,asTypeOf
用于断言minBound'
应该与fromInteger的结果具有相同的类型,而fromInteger的结果必须等于来自签名的a
maybeReadNum
。由此,编译器推断minBound'
的类型为a
。然后maxBound'
也应该具有相同的类型,因此它也会获得类型a
。这有点困难,使用ScopedTypeVariables
可能更容易也更好,但它可能是一种在编译器之间保留可移植性的方法(如果你还没有使用某些扩展)。