在我正在处理的库中,我有一个类似于以下的API:
data Collection a = Collection Seed {-etc...-}
type Seed = Int
newCollection :: Seed -> IO (Collection a)
newCollection = undefined
insert :: a -> Collection a -> IO () -- ...and other mutable set-like functions
insert = undefined
mergeCollections :: Collection a -> Collection a -> IO (Collection a)
mergeCollections (Collection s0 {-etc...-}) (Collection s1 {-etc...-})
| s0 /= s1 = error "This is invalid; how can we make it statically unreachable?"
| otherwise = undefined
Run Code Online (Sandbox Code Playgroud)
我希望能够强制用户不能打电话mergeCollections上Collection有不同的创建小号Seed值.
我想过尝试Collection使用类型级自然标记:我认为这意味着Seed必须在编译时静态知道,但我的用户可能是从环境变量或用户输入获取它,所以我不这样做认为这会奏效.
我也希望我能做一些像:
newtype Seed u = Seed Int
newSeed :: Int -> Seed u
newCollection :: Seed u -> IO (Collection u a)
mergeCollections :: Collection u a -> Collection u a -> IO (Collection u a)
Run Code Online (Sandbox Code Playgroud)
在某种程度上a以某种方式Seed用唯一类型标记,这样类型系统可以跟踪两个参数merge都是从同一个调用返回的种子创建的newSeed.要明确这个(手工波浪)计划a,b这里不知道如何:let a = newSeed 1; b = newSeed 1;.
这样的事情可能吗?
以下是我可以想象用户创建Seeds和Collections 的方法的一些示例.用户希望尽可能自由地使用其他操作(插入,合并等)与任何其他IO可变集合:
我们在程序期间只需要为所有 Collection s(动态)创建一个种子,但用户必须能够以某种方式指定在运行时如何从环境中确定种子.
从环境变量(或命令行参数)收集的一个或多个静态密钥:
main = do
s1 <- getEnv "SEED1"
s2 <- getEnv "SEED2"
-- ... many Collections may be created dynamically from these seeds
-- and dynamically merged later
Run Code Online (Sandbox Code Playgroud)可能不方便.为了处理仅在运行时已知的种子,您可以使用存在类型; 但是你不能静态地检查这两个存在包装的集合是否匹配.更简单的解决方案就是:
merge :: Collection a -> Collection a -> IO (Maybe (Collection a))
Run Code Online (Sandbox Code Playgroud)
另一方面,如果可以强制所有操作"一起"完成,在某种意义上,那么你可以做一些类似于STmonad 所做的事情:将所有操作组合在一起,然后提供"运行"所有操作只有当操作不通过要求它们在幻像变量上完全多态时才会泄漏集合的操作才有效,因此返回类型没有提到幻像变量.(Tikhon Jelvis在他的评论中也暗示了这一点.)这可能是这样的:
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Collection (Collection, COp, newCollection, merge, inspect, runCOp) where
import Control.Monad.Reader
type Seed = Int
data Collection s a = Collection Seed
newtype COp s a = COp (Seed -> a) deriving (Functor, Applicative, Monad, MonadReader Seed)
newCollection :: COp s (Collection s a)
newCollection = Collection <$> ask
merge :: Collection s a -> Collection s a -> COp s (Collection s a)
merge l r = return (whatever l r) where
whatever = const
-- just an example; substitute whatever functions you want to have for
-- consuming Collections
inspect :: Collection s a -> COp s Int
inspect (Collection seed) = return seed
runCOp :: (forall s. COp s a) -> Seed -> a
runCOp (COp f) = f
Run Code Online (Sandbox Code Playgroud)
请特别注意,不会导出COp和Collection构造函数.因此,我们永远不必担心一个人Collection会逃避它COp; runCOp newCollection不是很好的类型(尝试将集合"泄漏"到外部世界的任何其他操作将具有相同的属性).因此,不可能将Collection具有一个种子的构造传递给merge在另一个种子的背景下操作.