2

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

在查看各种 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. MonadState每次调用操作 Snaplet 的函数时,数据都会出现。
    2. 数据存在于 a 中MonadState并包装在Snaplet包装器中。
    3. 有一个像instance HasSubSnaplet MySnaplet这样的类+实例,它具有用于获取 Snaplet 数据的功能,MySnaplet前提MySnaplet是 aMonadState在调用该函数时位于 a 中。
    4. 3.中的函数有类型MySnaplet -> Snaplet SubSnaplet
    5. 有一个像 3. 中那样的类+实例,它提供了一个Lens MySnaplet (Snaplet SubSnaplet).
    6. 类+实例需要一个Lens (Snaplet MySnaplet) (Snaplet SubSnaplet).
    7. class+instance 假定它MySnaplet是应用程序的“顶级 Snaplet”,并且需要绝对镜头/参考,因此MySnaplet必须bMonadSnaplet.

如我所见,如果 Snaplet 是只读的,则引用类型 1. 有意义,如果 Snaplet 需要更改,则引用类型 2. 有意义。

MySnaplet此外,当只能有一个而没有更多时,为该方法设置一个类是有意义的SubSnaplet,并且具有绝对引用对于数据库之类的东西可能是有意义的,因为只有顶级 Snaplet 才能访问凭据和什么不是。但是,作为 Snaplet 编写器做出这种假设可能是错误的,使用相对引用来代替也没有任何缺点。

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

对我来说,参考类型 2. 和方法 1、2、5 或 6 中的一种似乎在所有情况下都是最有意义的,而且我认为没有理由为什么一直没有就只使用 eg (2, 1) 达成共识.

所以:

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

现有的所有 Snaplets 还没有使用相同的参考方法的原因是什么(即使在核心snap包中,也使用了大量不同的方法)?

4

1 回答 1

1

TLDR;大多数时候,您可能想要使用with函数和相关镜头。

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

由于类型类是完全可选的,并且与镜头的选择大致正交,因此对于这个答案的其余部分,我将重点关注没有类型类的情况。

API 的目的是您使用镜头(而不是让您将某些东西包装在 Snaplet 数据类型中的“普通引用”)来访问您的状态。这是因为在请求处理过程中改变状态是我们希望 Snaplets 提供的基本能力。在大多数情况下,我们打算Snaplet成为最终用户不需要触摸的不透明包装器,除非在他们的 snaplet 的状态类型中。这就是为什么 MonadState 实例无需 Snaplet 包装器即可让您直接进入您的类型。

话虽如此,有四种基本的访问模式。我们可以看到这些查看MonadSnaplet 类型的类

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 的 data-lens-template 包自动为您生成的镜头,使用起来最自然。因此,这是推荐的模式。with(因此是更短的、无底漆的名称。)和之间的区别在于withTop采用with相对镜头和withTop采用绝对镜头。大多数时候我使用相对镜头。但我们希望允许使用绝对镜头,因为我可以想象复杂的应用程序,其中一个 snaplet 可能需要获取另一个 snaplet 提供的东西,但不是当前 snaplet 的后代。

有时,您希望能够拥有一个身份镜头。这需要一个Lens (Snaplet a) (Snaplet b). 所以后两个引物功能类似于前两个,只是它们采用这种镜头。

于 2012-01-20T17:00:48.643 回答