使用Haskell的类型系统来强制实现模块化

Bil*_*ill 15 haskell types type-systems

我正在考虑如何使用Haskell的类型系统来强制执行程序中的模块化.举例来说,如果我有一个Web应用程序,我很好奇,如果有从纯代码分开CGI代码从文件系统代码的所有数据库代码的方式.

例如,我正在设想一个DB monad,所以我可以编写如下函数:

countOfUsers :: DB Int
countOfUsers = select "count(*) from users"
Run Code Online (Sandbox Code Playgroud)

我希望不可能使用DB monad支持的副作用.我描绘出了一个更高级别的单子,将被限制在直接URL处理器,将能够撰写的DB单子和IO单子电话.

这可能吗?这是明智的吗?

更新:我最终用Scala而不是Haskell实现了这个目标:http://moreindirection.blogspot.com/2011/08/implicit-environment-pattern.html

Don*_*art 13

我描绘出了一个更高级别的单子,将被限制在直接URL处理器,将能够撰写的DB单子和IO单子电话.

您当然可以实现这一点,并获得关于组件分离的非常强大的静态保证.

最简单的是,您需要一个受限制的IO monad.使用类似"污点"技术的东西,您可以创建一组IO操作,将其移植到一个简单的包装器中,然后使用模块系统隐藏类型的底层构造函数.

通过这种方式,您只能在CGI上下文中运行CGI代码,在DB上下文中运行DB代码.Hackage有很多例子.

另一种方法是为操作构造解释器,然后使用数据构造函数来描述您希望的每个基本操作.这些操作仍然应该构成一个monad,你可以使用do-notation,但你要建立一个描述要运行的动作的数据结构,然后通过解释器以受控的方式执行.

在典型情况下,这可能比您需要的内省更多内省,但该方法确实为您提供了在执行之前检查用户代码的全部功能.


gla*_*erl 5

我认为除了提到的两个唐斯图尔特之外还有第三种方式,甚至可能更简单:

class Monad m => MonadDB m where
    someDBop1 :: String -> m ()
    someDBop2 :: String -> m [String]

class Monad m => MonadCGI m where
    someCGIop1 :: ...
    someCGIop2 :: ...

functionWithOnlyDBEffects :: MonadDB m => Foo -> Bar -> m ()
functionWithOnlyDBEffects = ...

functionWithDBandCGIEffects :: (MonadDB m, MonadCGI m) => Baz -> Quux -> m ()
functionWithDBandCGIEffects = ...

instance MonadDB IO where
    someDBop1 = ...
    someDBop2 = ...

instance MonadCGI IO where
    someCGIop1 = ...
    someCGIop2 = ...
Run Code Online (Sandbox Code Playgroud)

这个想法非常简单,您可以为要分离的各种操作子集定义类型类,然后使用它们对函数进行参数化.即使你创建类的唯一具体monad是IO,在任何MonadDB上参数化的函数仍然只允许使用MonadDB操作(以及从它们构建的操作),因此您可以获得所需的结果.在IO monad中的"可以执行任何操作"功能中,您可以无缝地使用MonadDB和MonadCGI操作,因为IO是一个实例.

(当然,如果你愿意,你可以定义其他实例.通过各种monad变换器来提升操作将是直截了当的,我认为实际上并没有什么能阻止你为"包装器"和"解释器"monad Don Stewart编写实例提到,从而结合了这些方法 - 虽然我不确定你是否有理由这么做.)