Я хотел бы записать безопасную версию toEnum
:
safeToEnum :: (Enum t, Bounded t) => Int -> Maybe t
Наивная реализация:
safeToEnum :: (Enum t, Bounded t) => Int -> Maybe t
safeToEnum i =
if (i >= fromEnum (minBound :: t)) && (i <= fromEnum (maxBound :: t))
then Just . toEnum $ i
else Nothing
main = do
print $ (safeToEnum 1 :: Maybe Bool)
print $ (safeToEnum 2 :: Maybe Bool)
И это не работает:
safeToEnum.hs:3:21:
Could not deduce (Bounded t1) from the context ()
arising from a use of `minBound' at safeToEnum.hs:3:21-28
Possible fix:
add (Bounded t1) to the context of an expression type signature
In the first argument of `fromEnum', namely `(minBound :: t)'
In the second argument of `(>=)', namely `fromEnum (minBound :: t)'
In the first argument of `(&&)', namely
`(i >= fromEnum (minBound :: t))'
safeToEnum.hs:3:56:
Could not deduce (Bounded t1) from the context ()
arising from a use of `maxBound' at safeToEnum.hs:3:56-63
Possible fix:
add (Bounded t1) to the context of an expression type signature
In the first argument of `fromEnum', namely `(maxBound :: t)'
In the second argument of `(<=)', namely `fromEnum (maxBound :: t)'
In the second argument of `(&&)', namely
`(i <= fromEnum (maxBound :: t))'
А также я понимаю сообщение, компилятор не распознает это minBound
и maxBound
должен произвести точно тот же тип как в типе результата safeToEnum
inspite явного описания типа (:: t
). Какая-либо идея, как зафиксировать его?
Решенный
И работа решений camccann и Dave (хотя Dave нужно быть скорректирован). Спасибо вам обоим (но я мог принять только один). Рабочий пример с ScopedTypeVariables:
{-# LANGUAGE ScopedTypeVariables #-}
safeToEnum :: forall t . (Enum t, Bounded t) => Int -> Maybe t
safeToEnum i =
if (i >= fromEnum (minBound :: t)) && (i <= fromEnum (maxBound :: t))
then Just . toEnum $ i
else Nothing
Переменные типа с областью видимости здесь не нужны, вам просто нужно дать понять GHC, что вы ожидаете, что все элементы Enum
будут быть одного типа. Это легко сделать, передав их все функции, которая явно принимает различные Enum
одного типа. Вот один из способов:
enumIfBetween :: (Enum a) => a -> a -> Int -> Maybe a
enumIfBetween a z x = let a' = fromEnum a
z' = fromEnum z
in if a' <= x && x <= z'
then Just $ toEnum x
else Nothing
safeToEnum i = enumIfBetween minBound maxBound i
main = do
print $ (safeToEnum 1 :: Maybe Bool)
print $ (safeToEnum 2 :: Maybe Bool)
Попробовать в GHCi:
> main
Just True
Nothing
Более общим решением, использующим тот же принцип, является стандартная библиотечная функция asTypeOf
, которая имеет то же поведение, что и const
, но требует, чтобы оба аргумента были одного типа:
safeToEnum :: (Enum t, Bounded t) => Int -> Maybe t
safeToEnum i = let r = toEnum i
max = maxBound `asTypeOf` r
min = minBound `asTypeOf` r
in if i >= fromEnum min && i <= fromEnum max
then Just r
else Nothing
Эта версия также работает.
Имейте в виду, что ScopedTypeVariables
- это расширение языка, поэтому его не обязательно переносить между компиляторами. На практике почти все используют GHC, но обычно предпочитают придерживаться стандартного базового языка (например, Haskell 98), когда это возможно. В этом случае ScopedTypeVariables
действительно перебор; вики Haskell предлагает asTypeOf
в качестве переносимой замены для такого рода сценариев.
Вам нужно использовать переменные с ограниченным типом
{-# LANGUAGE ScopedTypeVariables #-}
safeToEnum :: (Enum t, Bounded t) => Int -> Maybe t
safeToEnum i =
if (i >= fromEnum (minBound :: t)) && (i <= fromEnum (maxBound :: t))
then Just . toEnum $ i
else Nothing
main = do
print $ (safeToEnum 1 :: Maybe Bool)
print $ (safeToEnum 2 :: Maybe Bool)
Без него t
означает для всех t. т
.