1

(更新)

我已经使用Free Monad为通用数据存储创建了一个接口。我想将用户在运行时选择的特定解释器(:: DataStore a -> IO a)连同其他一些信息一起放入状态单子中。我似乎无法在数据结构中的该字段中添加任何内容。

如何将值放入定义为更高级别类型的字段中?

下面是一个最小的例子:

{-# LANGUAGE RankNTypes, DeriveFunctor #-}

data ProgramState = PS { -- line 3
    [...]
  , storageInterface :: (forall a. DataStore a -> IO a)
  }

data DataStoreF next = 
     Create    Asset                           ( String -> next)
  |  Read      String                          ( Asset  -> next)
  |  Update    Asset                           ( Bool   -> next)
  |  UpdateAll [Asset]                         ( Bool   -> next)
  |  [...]
  deriving Functor

type DataStore = Free DataStoreF

runMemory :: (IORef (Map String Asset)) -> DataStore a -> IO a
runMemory ms (Pure a) = return a
runMemory ms (Free Create asset next) = [...]
runMemory ms (Free Read   str   next) = [...]
[...]

pickStorageInterface :: IO (DataStore a -> IO a)
pickStorageInterface = do
  opts <- parseOptions
  case (storage opts) of
     MemoryStorage -> 
       ms <- readAssetsFromDisk 
       return $ runMemory ms
     SomeOtherStorage -> [...]

restOfProgram :: StateT ProgramState IO
restOfProgram = [...]

main = do
  si <- pickStorageInterface
  let programState = PS { storageInterface = si} -- line 21
  evalState restOfProgram programState

当我尝试这样做时,GHC 抱怨说:

Main.hs: << Line 21 >>
Couldn't match type `a0' with `a'
  because type variable `a' would escape its scope
This (rigid, skolem) type variable is bound by
  a type expected by the context: DataStore a -> IO a
  at Main.hs <<line 3>>
Expected type: DataStore a -> IO a
  Actual type: DataStore a0 -> IO a0
In the `storageInterface' field of a record
  [...]

更新

我最初的最小示例是最小的。一些进一步的实验表明,当我需要在 IO monad 中加载接口以便读取命令行选项时,就会出现问题。我已更新示例以包含该问题。知道了这一点,我也许可以围绕它进行编码。

有趣的 GHCI 告诉我类型函数的结果IO (DataStore a -> IO a)不是DataStore GHC.Prim.Any -> IO GHC.Prim.Any我所期望的。

4

1 回答 1

2

这里的问题是

pickStorageInterface :: forall a. IO (DataStore a -> IO a)

而我们需要 (impredicative) 类型

pickStorageInterface :: IO (forall a. DataStore a -> IO a)

上面的代码工作。唉,在 GHC 中,禁言类型现在处于可悲的状态,最好避免。

您可以使用newtype围绕通用量化类型的包装器来解决此问题:

newtype SI = SI { runSI :: forall a. DataStore a -> IO a }

pickStorageInterface :: IO SI
pickStorageInterface = do
  opts <- parseOptions
  case (storage opts) of
     MemoryStorage -> 
       ms <- readAssetsFromDisk 
       return $ SI $ runMemory ms
     ...

main = do
  si <- pickStorageInterface
  let programState = PS { storageInterface = runSI si}
  ...
于 2016-07-29T08:00:41.307 回答