在 Haskell 中创建 Semigroup 数据类型实例

Creating a Semigroup data type instance in Haskell

可能的目标是为 Haskell 中新定义的数据类型创建一个新的 Semigroup 类型 class 实例(对于那些知道 "Get programming with Haskell" book by Will Kurt 的人,我可能会向您推荐第 428 页,即带有练习扩展的顶点项目 5 的结尾)。

有一个新定义的数据类型:

data HINQ m a b = HINQ (m a -> m b) (m a) (m a -> m a)
                | HINQ_ (m a -> m b) (m a)

此数据类型指定类似SQL的查询,其中m定义上下文(Monad或Alternative),(m a -> m b)是目标类似于[=39=的函数] 函数 SELECT,即定义了人们希望在数据库中看到的 属性 类型,(m a) 是一个应用前一个函数的“table”(类似于 SQL 的 table_name) 最后 (m a -> m a) 过滤掉正在寻找的 属性 (类似于 SQL 的 WHERE)。

我的目标是使此数据类型成为半群(最后是幺半群)的实例。值得一提的是,假定 ab 等所需的所有 Semigroup 实例。

instance (Semigroup a, Semigroup (m a), Semigroup b,...) => 
          Semigroup (HINQ m a b) where
  (<>) (HINQ func1 start1 test1)
       (HINQ func2 start2 test2) =

所以它的粗略想法(在背景上看得更清楚)是可以将对数据库的几个不同查询组合成一个查询,但我想不出如何同时合并两个 (m a -> m b) 类型的不同函数合并两个 tables (m a)...第一个想法是将它们组合成列表,但随后类型签名发生了变化我还没有还没找到解决办法。

我想你不想要 Semigroup。组合 all 对查询是没有意义的——只有其中一个的输出类型与另一个的输入类型合理匹配的查询!幸运的是,我们有一个概念对应于 Semigroup 的“类型化”变体(实际上是类型化的 Monoid,但足够接近):Category.

此外,我认为将查询与您正在查询的 table 相结合是一个设计错误。它们在概念上是独立的概念;实际上,在组合两个查询时,您仍然只有一个 table,而不是两个。所以:

data HINQ m a b = HINQ (m a -> m b) (m a -> m a)

instance Category (HINQ m) where
    id = HINQ id id
    HINQ slct whr . HINQ slct' whr' = HINQ (slct . whr . slct') whr'

恒等律很清楚,但是左右WHERE子句用法的不对称看起来有点可疑,所以我们应该仔细检查结合律:

(HINQ s0 w0 . HINQ s1 w1) . HINQ s2 w2
= HINQ (s0 . w0 . s1) w1 . HINQ s2 w2
= HINQ (s0 . w0 . s1 . w1 . s2) w2
= HINQ s0 w0 . HINQ (s1 . w1 . s2) w2
= HINQ s0 w0 . (HINQ s1 w1 . HINQ s2 w2)

看起来不错!

编辑

呃,嗯...也许 x . id = x 法律毕竟不是那么明确。哎呀!这可能无法修复,除非您只考虑包含函数的组合的相等性,在这种情况下,为什么不直接对函数使用 Category 实例呢?当然,您还有另一种选择,即不要求保持身份法则。这有点不寻常,但我想这是否合理在很大程度上取决于您的用例。

如果你更明确地表示你的过滤,组合可能会更容易,例如作为 a -> Boola -> m Bool 而不是 m a -> m a。这使您有更多机会在 (.) 实现中组合两个过滤器,而不是像上面的实例那样将其中一个过滤器滚动到 select 操作中。