【问题标题】:Creating a Semigroup data type instance in Haskell在 Haskell 中创建 Semigroup 数据类型实例
【发布时间】:2021-09-16 03:27:56
【问题描述】:

可能的目标是为 Haskell 中新定义的数据类型创建一个新的 Semigroup 类型类实例(对于那些知道 "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)是目标类似于SQL函数SELECT的函数,即定义了想要在数据库中看到的属性类型,(m a) 是一个“表”,它应用了前一个函数(类似于 SQL 的 table_name),最后(m a -> m a) 过滤掉了一个是寻找(类似于 SQL 的WHERE)。

我的目标是让这个数据类型成为一个半群(最后是一个 Monoid)的实例。值得一提的是,假设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 -&gt; m b) 类型的不同功能同时合并两个表 (m a)... 第一个想法是将它们组合成列表,但是类型签名发生变化我还没有找到解决方案。

【问题讨论】:

  • 我不太了解上下文 - 特别是 HINQ 的两个不同的数据构造函数 - 所以这可能会导致您找到错误的解决方案,但如果 m 是 Applicative (它将如果你说它是 Monad 或 Alternative)并且 a 是一个半群,那么 m a 可以通过 liftA2 (&lt;&gt;) 自动成为一个半群。 (不知道如何证明这必然是关联的,但我确信它一定会以某种方式脱离应用法则。)
  • 这种类型当然可以写一个Semigroup的实例,甚至可以中规中矩。 (事实上​​,有一个微不足道的问题 - 为 3 元组的每个组件提升现有的 Semigroup 实例)。但是这个实例不是唯一的,所以Semigroup 法律不会在这里指导您。您应该首先为HINQ 写出几个示例值,然后确定您希望它们的半群加法结果的样子。
  • @user2407038,我想我明白了,主要问题是为(m a -&gt; m b) 建立 Semigroup 实例,它(出于后台目的)应该采用“数据库”,例如一些数据aList 并提取所需的信息并以List b 的形式返回此数据。问题是如果我有两个不同的函数 f1f2 类型为(m a -&gt; m b) 和两个类型为m a 的数据集(让我们暂时忘记第三个) 然后我想将这些函数和数据集合并到一个对象中,以便将两个查询表示为一个。
  • @RobinZigmond,感谢提升的想法,现在我应该了解如何将此提升与为第一个函数创建 Semigroup 实例保持一致。对于数据承包商,让我们首先认为好像只有第二个,所以没有最后一个函数。
  • 暂时忘记泛型类型。对于[String] -&gt; [String] 类型的两个函数,这个操作是什么样的?例如drop 10 &lt;&gt; take 20 的结果是什么? reverse &lt;&gt; idreverse &lt;&gt; reverse 呢?就目前而言,您的问题未得到充分说明-&lt;&gt; 的许多实现都可以满足半群定律,并且对于上面的示例都做了不同的事情。你真正想要什么行为?

标签: haskell types semigroup


【解决方案1】:

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

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

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 -&gt; Boola -&gt; m Bool 而不是m a -&gt; m a。这让您有更多机会在 (.) 实现中组合两个过滤器,而不是像上面的实例那样将其中一个过滤器滚动到选择操作中。

【讨论】:

  • 我明白你的意思了,因为范畴论比 Haskell 更接近我。我还特别想定义一个Semigroup 实例,以及它是否合理。但是您并没有完全理解直接将 Category 实例用于函数的想法。您的意思是单独将它们视为特殊类型,即Category 的实例?
  • 顺便说一下@DanielWagner,在HINQ' (m a -&gt; m b) (m a) 上定义一个合成是否也合理,例如以下列方式:HINQ func1 table1 &lt;&gt; HINQ func2 table2 = HINQ (\ma -&gt; func1 table1 &lt;&gt; func2 table2) (table1 &lt;&gt; table2),其中m a 上的&lt;&gt; 是通过提升定义的?
  • @A.Gonus 我认为这不能满足恒等律,因为您的 lambda 中没有提到 ma。如果你愿意,你可以使用\ma -&gt; func1 ma &lt;&gt; func2 ma,那将是一个非常好的Monoid。 (实际上,这正是 (m a -&gt; m b, m a)Monoid 实例所做的。)
  • @A.Gonus 回复:“直接将 Category 实例用于函数”:如果您从我的回答中获取 HINQ 的定义,并且只考虑组成的相等性——那就是, HINQ f g = HINQ f' g' means f . g = f' . g' -- 那么真的没有理由为“select”和“where”阶段存储单独的函数。您可以将它们预先组合存储为f . gf' . g',然后函数的Category 实例已经进行了适当的组合; (f . g) . (f' . g')f . g . f' . g' 根据需要。
猜你喜欢
  • 2015-02-03
  • 2017-04-03
  • 1970-01-01
  • 2016-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-19
相关资源
最近更新 更多