引用子Snaplet的首选方法

时间:2012-01-13 15:10:21

标签: haskell haskell-snap-framework

Snap Framework中,Snaplet用于通过基于组件的界面将功能嵌入到其他Snaplet中:主Web应用程序是一个Snaplet,通过经典的“has-a”关系引用其他Snaplet,以及sub -Snaplets可以反过来引用其他Snaplet。

在查看各种Snaplet实现时,我已经看到使用不同的模式将Snaplet嵌入到父Snaplet中。具体做法是:

  • 有点参考。 Snaplet实现假定存在与父Snaplet的特定类型的关系。这是通过使用的参考方法强制执行的(见下文)。

    1. 一个简单的参考:

      data MySnaplet = MySnaplet { subSnaplet :: Snaplet SubSnaplet }
      
    2. 相对镜头:

      data MySnaplet = MySnaplet { _subSnaplet :: Snaplet SubSnaplet }
      
      subSnaplet :: Lens MySnaplet SubSnaplet
      subSnaplet = lens _subSnaplet $ \ a b -> a { _subSnaplet = b }
      
  • 参考方法。 Snaplet实现通过其接口强制实现访问Snaplet数据的特定方式,并且不同的Snaplet实现使用不同的方法。 Snaplet假定:

    1. 每次调用操作Snaplet的函数时,数据都会出现在MonadState中。
    2. 数据存在于MonadState中,并包含在Snaplet包装中。
    3. 有一个类instance HasSubSnaplet MySnaplet的类+实例,它具有从MySnaplet中获取Snaplet数据的功能,前提是此时MySnaplet位于MonadState调用函数。
    4. 3.中的函数改为MySnaplet -> Snaplet SubSnaplet
    5. 在3.中有一个类+实例,提供Lens MySnaplet (Snaplet SubSnaplet)
    6. 班级+实例需要Lens (Snaplet MySnaplet) (Snaplet SubSnaplet)
    7. 类+实例假定MySnaplet是应用程序的“顶级Snaplet”,并且需要绝对镜头/引用,因此MySnaplet必须是b中的MonadSnaplet {1}}。

正如我所看到的,如果Snaplet是只读的,那么引用类型1是有意义的。如果Snaplet需要更改,则有意义。

此外,当MySnaplet只能有一个SubSnaplet而没有更多时,有一个方法的类是有意义的,并且具有绝对引用可能对数据库之类的东西有意义,这些不可能被配置作为一个组件,假设只有顶级Snaplet可以访问凭据而不是。但是,将此假设作为Snaplet编写器可能是错误的,并且使用相对引用不会有任何缺点。

但是有一个proglem:Hackage上的现有Snaplet不符合我所做的这些假设;上述所有方法似乎都是随机和在各种情况下使用的。此外,我认为上述某些其他方面没有优势/劣势(例如需要Snaplet包装器)。

对我来说,参考类型2.方法1,2,5或6中的一个似乎在所有情况下最有意义,并且我认为没有理由仅为什么没有达成共识使用例如(2,1)一直。

所以:

作为Snaplet编写者,在编写新的Snaplet时应该首选哪种方法(假设它具有通用性),

为什么现有的所有Snaplet都没有使用相同的参考方法(即使在核心snap包中,也会使用大量不同的方法)? < / p>

1 个答案:

答案 0 :(得分:1)

TLDR;大多数情况下,您可能希望使用with功能和相对镜头。

使用HasSubSnaplet类型类是一个完全可选的模式,在没有多个SubSnaplet实例的情况下,可以减少“with subSnaplet”样板。我们选择为snap包中的Heist snaplet执行此操作,因为它对Heist snaplet有意义,并为用户提供模式的示例。

由于类型类是完全可选的并且与镜头的选择大致正交,因此对于本答案的其余部分,我将专注于没有类型类的操作。

API的目的是使用镜头(而不是“简单引用”来获取Snaplet数据类型中的内容)来访问您的状态。这是因为在请求处理过程中改变状态是我们希望Snaplet提供的基本能力。在大多数情况下,我们打算让Snaplet成为一个不透明的包装器,最终用户除了snaplet的状态类型之外不需要触摸它。这就是MonadState实例在没有Snaplet包装器的情况下直接将您带到您的类型的原因。

据说,有四种基本的访问模式。我们可以看到这些MonadSnaplet type class

with     :: Lens     v       (Snaplet v') -> m b v' a -> m b v a
withTop  :: Lens     b       (Snaplet v') -> m b v' a -> m b v a
with'    :: Lens (Snaplet v) (Snaplet v') -> m b v' a -> m b v a
withTop' :: Lens (Snaplet b) (Snaplet v') -> m b v' a -> m b v a

前两个功能中体现的镜头模式是由带有TemplateHaskell的数据镜头模板包自动生成的镜头类型,是最自然的。因此,这是推荐的模式。 (因此,较短的,未引用的名称。)withwithTop之间的区别在于with采用相对镜头而withTop采用绝对镜头。大多数时候我使用相对镜头。但我们想允许使用绝对镜头,因为我可以想象复杂的应用程序,其中一个snaplet可能需要获取另一个snaplet提供的东西,但不是当前snaplet的后代。

偶尔会出现您希望能够拥有身份镜头的情况。这需要Lens (Snaplet a) (Snaplet b)。因此,除了采用这种镜头之外,第二个两个引导功能类似于前两个功能。