Безопасный и полиморфный toEnum - PullRequest
8 голосов
/ 30 апреля 2010

Я хотел бы написать безопасную версию 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, несмотря на явное объявление типа (:: 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

Ответы [ 2 ]

13 голосов
/ 30 апреля 2010

Переменные типа Scoped здесь не нужны, вам просто нужно дать понять 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 в качестве переносной замены для такого рода сценариев.

3 голосов
/ 30 апреля 2010

Вам необходимо использовать переменные типа scoped

{-# 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 означает forall t. t.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...