我可以在运行时检查实例存在吗?

时间:2017-01-30 09:53:47

标签: haskell dependent-type

我有一个有限矢量数据族:

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实例的存在,有没有办法做到这一点?

2 个答案:

答案 0 :(得分:5)

我认为没有任何简单的方法。

Haskell的设计使得所有类型级别的东西都可以在运行时擦除。我知道的唯一方法是利用KnownNatTypeable并手动检查每个实例。例如。类似的东西:

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中转向运行时类型级别检查是非常不同寻常的。但是,在我们得到DataKindsKnownNat以及类似的东西之后,在运行时静态信息的实现/“反射”可能会变得更加有趣。

答案 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要使用的上下文中。