我正在考虑如何使用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
答案 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
你可以使用一些组合器来编写相同的东西,但我的观点是组合不那么优雅。