代码之家  ›  专栏  ›  技术社区  ›  Bill

使用haskell的类型系统强制模块化

  •  15
  • Bill  · 技术社区  · 15 年前

    我正在考虑如何使用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 回复  |  直到 13 年前
        1
  •  13
  •   Don Stewart    15 年前

    我正在描绘一个更高级别的monad,它将被限制为直接的url处理程序,并且能够组合对db monad和io monad的调用。

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

    最简单的说,你想要一个受限的io monad。使用类似于“污染”的技术,您可以创建一组提升到简单包装器中的io操作,然后使用模块系统隐藏类型的底层构造函数。

    这样,您只能在cgi上下文中运行cgi代码,在db上下文中运行db代码。关于黑客攻击有很多例子。

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

    在典型情况下,这可能会给您提供比所需更多的内省,但这种方法确实会让您在执行用户代码之前充分检查用户代码。

        2
  •  5
  •   glaebhoerl    13 年前

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

    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变形金刚提升操作是很简单的,我认为实际上没有什么可以阻止您为Stewart没有提到的“包装器”和“解释器”Monad编写实例,从而组合这些方法——尽管我不确定是否有如果你愿意的话。)

        3
  •  4
  •   Tomas Petricek    15 年前

    谢谢你的提问!

    我在一个客户机/服务器web框架上做了一些工作,该框架使用monad来区分不同的执行环境。显而易见的是 客户端 服务器端 ,但它也允许你写 两面 代码(可以在客户机和服务器上运行,因为它不包含任何特殊功能)以及 异步客户端 用于在客户机上编写非阻塞代码(本质上是客户机端的延续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
    

    你可以用一些组合词来写同样的东西,但我的观点是构图不那么优雅。