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

时间:2010-03-05 00:37:10

标签: haskell types type-systems

我正在考虑如何使用Haskell的类型系统来强制执行程序中的模块化。例如,如果我有一个Web应用程序,我很好奇是否有办法将所有数据库代码与CGI代码从纯文本系统代码中分离出来。

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

countOfUsers :: DB Int
countOfUsers = select "count(*) from users"

我希望不可能使用DB monad支持的副作用。我正在想象一个更高级别的monad,它将仅限于直接URL处理程序,并且能够组成对DB monad和IO monad的调用。

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

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

3 个答案:

答案 0 :(得分:13)

  

我正在想象一个更高级别的monad,它将仅限于直接URL处理程序,并且能够组成对DB monad和IO monad的调用。

你当然可以做到这一点,并获得关于组件分离的非常强大的静态保证。

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

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

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

在典型情况下,这可能比您需要的内省更多,但是在执行之前,该方法可以让您充分考虑用户代码。

答案 1 :(得分: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 = ...

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

(当然,你可以定义其他实例,如果你愿意的话。通过各种monad变换器提升操作将是直截了当的,我认为实际上并没有什么能阻止你为实例编写实例“包装”和“翻译”单子唐斯图尔特提到,从而结合了这些方法 - 虽然我不确定你是否有理由这样做。)

答案 2 :(得分:4)

感谢您提出这个问题!

我在客户端/服务器Web框架上做了一些工作,它使用monads来区分不同的exect环境。显而易见的是客户端服务器端,但它也允许您编写双方代码(可以在两者上运行)客户端和服务器,因为它不包含任何特殊功能)以及异步客户端,它用于在客户端编写非阻塞代码(本质上是客户端的延续monad) 。这听起来与您区分CGI代码和DB代码的想法非常相关。

以下是有关我的项目的一些资源:

我认为这是一种有趣的方法,它可以为您提供有关代码的有趣保证。但是有一些棘手的问题。如果你的服务器端函数需要int并返回int,那么这个函数的类型应该是什么?在我的项目中,我使用了int -> int server(但也可以使用server (int -> int)

如果你有这样的几个函数,那么组合它们并不是那么简单。您需要编写以下代码,而不是编写goo (foo (bar 1))

do b <- bar 1
   f <- foo b
   return goo f

你可以使用一些组合器来编写相同的东西,但我的观点是组合不那么优雅。