Haskell:有限类型的用户定义值?

时间:2016-07-20 16:26:44

标签: haskell types

Haskell的部分功能是将范围检查委托给类型系统(参见例如Numeric.Natural)。对于在运行时定义一组值的类型,这可以做到吗?我有效地喜欢Enum,它的值在编译时是未知的。

编辑:就示例用法而言:

-- Defines the list of allowed values
init :: [a] -> ?

-- Constructs a new instance
construct :: a -> ? -> Maybe Foo

-- Then just usable like an enum
bar :: Int -> Foo -> Bar

理想情况下,我也可以使用Bounded之类的内容。

2 个答案:

答案 0 :(得分:2)

遗憾的是,您的示例代码太稀疏,无法表明您的真实含义。我猜测你可能在依赖类型之后,如n.m. suggested。如果是这种情况,你可能最好不要再看看像Agda而不是Haskell这样的东西。如果您想要Daniel Wagner suggested更安全的版本,可以使用reflection包来获取它。

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}

module DynEnum (.... not including newtype constructors) where

import Data.Reflection
import Data.Proxy
import Data.Set (Set, splitMember, size, lookupIndex, fromList, elemAt, member, findMin, findMax)
import Data.Foldable
import Data.Bool
import Data.Type.Coercion

-- Just Enum
newtype Limited a s = Limited { unLimited :: a }

type role Limited representational nominal

-- We can safely conflate types of values that come
-- from the same set.
coerceLimited :: (Reifies s (Set a), Reifies t (Set a), Ord a)
              => Maybe (Coercion (Limited a s) (Limited a t))
coerceLimited
  | reflect (Proxy :: Proxy s) == reflect (Proxy :: Proxy t)
      = Just Coercion
  | otherwise = Nothing

instance (Ord a, Reifies s (Set a)) => Enum (Limited a s) where
  toEnum i
    | 0 <= i && i < size values = Limited $ elemAt i values
    | otherwise = error "Limited toEnum: out of range"
    where values = reflect (Proxy :: Proxy s)
  fromEnum x = case lookupIndex (unLimited x) (reflect x) of
                 Nothing -> error "Limited fromEnum: out of range"
                 Just i -> i
  enumFrom (Limited a) = case splitMember a (reflect (Proxy :: Proxy s)) of
                 (_, False, s) -> fmap Limited $ toList s
                 (_, True, s) -> Limited a : fmap Limited (toList s)
  enumFromTo (Limited a) (Limited b) = case splitMember a (reflect (Proxy :: Proxy s)) of
                 (_, inclFirst, s) -> case splitMember b s of
                   (t, inclLast, _) -> bool id (Limited a:) inclFirst
                                        . (map Limited (toList t) ++)
                                        $ bool [] [Limited b] inclLast

initialize :: Ord a
           => [a]
           -> (forall s . Enum (Limited a s) => Proxy s -> r)
           -> r
initialize vals f = reify (fromList vals) f

construct :: forall s a . (Ord a, Reifies s (Set a)) => a -> Maybe (Limited a s)
construct x
  | x `member` reflect (Proxy :: Proxy s) = Just (Limited x)
  | otherwise = Nothing

newtype Bound a b = Bound a deriving (Enum)

type role Bound representational nominal

instance Reifies b (a, a) => Bounded (Bound a b) where
  minBound = Bound . fst $ reflect (Proxy :: Proxy b)
  maxBound = Bound . snd $ reflect (Proxy :: Proxy b)

initializeBounded :: (a, a)
                  -> (forall b . Bounded (Bound a b) => Proxy b -> r)
                  -> r
initializeBounded bounds f = reify bounds f

newtype LimitedB a s b = LimitedB (Bound (Limited a s) b)

deriving instance (Ord a, Reifies s (Set a)) => Enum (LimitedB a s b)
deriving instance Reifies b (Limited a s, Limited a s) => Bounded (LimitedB a s b)

initializeLimitedB :: Ord a
                   => [a]
                   -> (forall s b . (Enum (LimitedB a s b), Bounded (LimitedB a s b)) => Proxy s -> Proxy b -> r)
                   -> r
initializeLimitedB [] _f = error "Cannot initialize LimitedB with an empty list"
initializeLimitedB vals f = reify set $ \ps ->
                     reify (Limited (findMin set), Limited (findMax set)) $ \pb ->
                     f ps pb
  where
    set = fromList vals

答案 1 :(得分:1)

也许Set适合您的需求。我们有:

initialize :: Ord a => [a] -> Set a
initialize = fromList

construct :: Ord a => a -> Set a -> Maybe a
construct x xs = guard (x `member` xs) >> return x

dynamicMinBound :: Set a -> Maybe a
dynamicMinBound xs = fst <$> minView xs

dynamicMaxBound :: Set a -> Maybe a
dynamicMaxBound xs = fst <$> maxView xs

enumerate :: Set a -> [a]
enumerate = toList

dynamicToEnum :: Int -> Set a -> Maybe a
dynamicToEnum n xs = guard (inRange n (0, size xs-1)) >> return (elemAt n xs)

dynamicFromEnum :: Ord a => a -> Set a -> Maybe Int
dynamicFromEnum = lookupIndex

我相信这涵盖了您所要求的操作,但我很容易误解某些内容 - 您的规范并非100%明确。