无法使用单例库将类型级列表转换回值级

Unable to convert type-level list back to value-level using singletons library

我正在尝试编写 合理地 静态检查授权系统 [1],并且目前正在努力编写一个将从类型中提取所需权限的函数-level annotation/phantom 到 value-level.

{-# LANGUAGE DataKinds, GADTs, ScopedTypeVariables #-}

module Try5 where

import Control.Monad.Reader
import Data.Singletons
import Data.Singletons.TH


data Permission = PermA
                | PermB
                deriving (Eq, Show)
$(genSingletons [''Permission])

data Env = Env

newtype AppM (perms :: [Permission]) a = AppM (ReaderT Env IO a) deriving (Functor, Applicative, Monad, MonadIO, MonadReader Env)

-- other functions for constructing an action in `AppM perms`
-- have been removed for brevity

runAction :: AppM (perms :: [Permission]) () -> IO ()
runAction _ = do
  let permissions :: [Permission] = fromSing $ singByProxy (Proxy :: Proxy (perms :: [Permission]))
  putStrLn $ "Huzzah, I freed the permissions from the type-level cage: " <> (show permissions)
  pure ()

错误:

    • Ambiguous type variable ‘a0’ arising from a use of ‘singByProxy’
      prevents the constraint ‘(SingI a0)’ from being solved.
      Probable fix: use a type annotation to specify what ‘a0’ should be.
      These potential instances exist:
        instance forall a (n1 :: a) (n2 :: [a]).
                 (SingI n1, SingI n2) =>
                 SingI (n1 : n2)
          -- Defined in ‘singletons-2.4.1:Data.Singletons.Prelude.Instances’
        instance SingI '[]
          -- Defined in ‘singletons-2.4.1:Data.Singletons.Prelude.Instances’
    • In the second argument of ‘($)’, namely
        ‘singByProxy (Proxy :: Proxy (perms :: [Permission]))’
      In the expression:
        fromSing $ singByProxy (Proxy :: Proxy (perms :: [Permission]))
      In a pattern binding:
        permissions :: [Permission]
          = fromSing $ singByProxy (Proxy :: Proxy (perms :: [Permission]))
   |
24 |   let permissions :: [Permission] = fromSing $ singByProxy (Proxy :: Proxy (perms :: [Permission]))
   |                                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[1] 可以在

找到更多上下文

perms 不在 runAction 正文的范围内。它需要与 forall 显式绑定。见 doc on ScopedTypeVariables.

另一个问题是 "demote" 来自类型的值需要一个 SingI 实例。

关键的直觉是 forall 引入了 run-time 不相关的变量:如果 runAction :: forall p. ... 没有任何约束,runAction @p 实际上不能依赖于 p,它必须总是做同样的事情。理查德·艾森伯格 (Richard Eisenberg) 的论文 Dependent types in Haskell: Theory and Practice 提供了有关此事的更多详细信息(第 4.2 节)。

因此 runAction 的类型应该是这样的:

runAction :: forall perms. SingI perms => AppM perms () -> IO ()