我有一个有限矢量数据族:
data family Vec (n :: Nat) e
data instance Vec 2 Float = Vec2f !Float !Float
data instance Vec 3 Float = Vec3f !Float !Float !Float
-- and so on
我也有一系列的getter函数:
class Get (i :: Nat) (n :: Nat) e where
get :: Vec n e -> e
instance Get 0 2 Float where
get (Vec2f x _) = x
instance Get 1 2 Float where
get (Vec2f _ x) = x
instance Get 0 3 Float where
get (Vec3f x _ _) = x
instance Get 1 3 Float where
get (Vec3f _ x _) = x
instance Get 2 3 Float where
get (Vec3f _ _ x) = x
-- and so on
这样,在编译时检查对向量元素的访问,因此编译:
get @0 (Vec2f 0 1)
并且没有:
get @4 (Vec2f 0 1)
现在,我想知道是否可以使用这些函数编写运行时范围检查。 我试过这个:
get' :: forall n e. (KnownNat n) => Integer -> Vec n e -> Maybe e
get' i v =
if i >= 0 && i < natVal (Proxy :: Proxy n)
then (flip fmap) (someNatVal i) $ \(SomeNat (Proxy :: Proxy i)) ->
get @i v -- Could not deduce (Get i n e)
else Nothing
我想我必须在运行时检查Get i n e
实例的存在,有没有办法做到这一点?
答案 0 :(得分:5)
我认为没有任何简单的方法。
Haskell的设计使得所有类型级别的东西都可以在运行时擦除。我知道的唯一方法是利用KnownNat
和Typeable
并手动检查每个实例。例如。类似的东西:
get' :: forall n e. (Typeable e, KnownNat n) => Integer -> Vec n e -> Maybe e
get' 0 v =
case sameNat (Proxy @n) (Proxy @2) of
Just Refl -> case eqT :: Maybe (e :~: Float) of
Just Refl -> Just $ get @0 v
Nothing -> Nothing -- TODO check for e/=Float
Nothing -> case sameNat (Proxy @n) (Proxy @3) of
Just Refl -> case eqT :: Maybe (e :~: Float) of
Just Refl -> Just $ get @0 v
Nothing -> Nothing -- TODO check for e/=Float
Nothing -> Nothing -- TODO check for n/=2,3
get' i v = Nothing -- TODO check for i>0
(这可能会使用一些因子...)
可能更好的方法是定义一个列表(或其他)的具体化词典,然后循环它。但是,人们需要保持这样的列表与实际实例同步。
也许将来Haskell也会在运行时获得类型类搜索机制。到目前为止,我认为这还没有完成,因为在Haskell中转向运行时类型级别检查是非常不同寻常的。但是,在我们得到DataKinds
和KnownNat
以及类似的东西之后,在运行时静态信息的实现/“反射”可能会变得更加有趣。
答案 1 :(得分:1)
现在的一个问题是Get
的实例都是临时的,因此很难提供一个干净的解决方案。如果你愿意稍微重构Get
,那就简单多了:
class Get (n :: Nat) e where
get :: (KnownNat i, i <= (n-1)) => Proxy i -> Vec n e -> e
instance Get 2 Float where
get :: (KnownNat i, i <= 1) => Proxy i -> Vec 2 Float -> Float
get p (Vec2f x y) = case natVal p of
0 -> x
1 -> y
_ -> error ":("
然后你可以做
get' :: forall n e. (Get n e, KnownNat (n - 1)) => Integer -> Vec n e -> Maybe e
get' i v = do
-- bring KnownNat i into context
SomeNat (iProx@Proxy) <- someNatVal i -- will be Nothing if i < 0
-- bring i <= (n - 1) into context
Refl <- iProx `isLE` (Proxy @(n - 1)) -- will be Nothing if i >= n
return (get iProx v)
使用typelits-witnesses库中的GHC.TypeLits.Compare将不等式约束带入GHC要使用的上下文中。