【问题标题】:How to write a Semigroup Instance?如何编写半群实例?
【发布时间】:2025-12-18 10:50:01
【问题描述】:

问题

给定一个数据类型,实现 Semigroup 实例。这是我要实现的数据类型: data Or a b = Fst a | Snd b deriving (Eq, Show, Num)。它应该像这样运行:

Prelude> Fst 1 <> Snd 2
Snd 2
Prelude> Fst 1 <> Fst 2
Fst 2
Prelude> Snd 1 <> Fst 2
Snd 1
Prelude> Snd 1 <> Snd 2
Snd 1

当我测试像&gt; Fst "help" &lt;&gt; Fst "me" 这样的值时,它可以正常工作,但是当我测试其他值时,我会出错。当我尝试通过从错误中派生类来修复这些错误时,我会遇到更多错误。我在这里做错了什么?

我的代码

data Or a b =
    Fst a
  | Snd b
  deriving (Eq, Show)

instance (Semigroup a, Semigroup b, Num a, Num b) => Semigroup (Or a b) where
 (Snd a) <> _ = Snd a
  _ <> (Snd a) = Snd a
  (Fst a) <> (Fst b) = Fst b  

错误

当我尝试使用整数 &gt; Fst 1 &lt;&gt; Fst 2 进行测试时,我得到:

No instance for (Num a0) arising from a use of ‘it’
The type variable ‘a0’ is ambiguous
Note: there are several potential instances:
  instance RealFloat a => Num (Data.Complex.Complex a)
    -- Defined in ‘Data.Complex’
  instance Data.Fixed.HasResolution a => Num (Data.Fixed.Fixed a)
    -- Defined in ‘Data.Fixed’
  instance forall (k :: BOX) (f :: k -> *) (a :: k).
           Num (f a) =>
           Num (Data.Monoid.Alt f a)
    -- Defined in ‘Data.Monoid’
  ...plus 21 others
In the first argument of ‘print’, namely ‘it’
In a stmt of an interactive GHCi command: print it

当我尝试派生 Num 类 data Or a b = Fst a | Snd b deriving (Eq, Show, Num) 时,我得到:

    Can't make a derived instance of ‘Num (Or a b)’:
      ‘Num’ is not a derivable class

    In the data declaration for ‘Or’
Failed, modules loaded: none.

【问题讨论】:

    标签: haskell functional-programming monoids


    【解决方案1】:

    真正的问题是您对实例施加的约束,而您不需要。随便写

    instance Semigroup (Or a b) where
      (Snd a) <> _ = Snd a
      _ <> (Snd a) = Snd a
      (Fst a) <> (Fst b) = Fst b
    

    正如 chepner 所示,您实际上可以减少行数和模式数,并返回其中一个参数作为结果,而不是构造它的新副本。这可能会提高效率。

    不太重要的是,模式中的括号都是多余的,因为应用程序的优先级高于任何运算符。

    【讨论】:

    • 编译器是否足够聪明,可以识别Snd a 何时被未经修改地重用以避免构造新副本?我不聪明。我只是逐字复制了定义作为类似实例的已发布示例。 :)
    • @chepner,我不确定。我记得,这并不一定会发生在核心到核心中,而且我对 STG 及其他领域发生的事情不太熟悉。
    【解决方案2】:

    在您的定义中,对于任何特定类型类的实例都不需要 ab,因为您实际上从未对包装的值做任何事情。

    instance Semigroup (Or a b) where
        (Snd a) <> _ = Snd a
        _       <> (Snd a) = Snd a
        (Fst a) <> (Fst b) = Fst b  
    

    由于您的 Or 类型与 Either 同构,请将其与 semigroups 包中的 Either 实例进行比较,这同样对所涉及的类型没有任何限制。

    instance Semigroup (Either a b) where
      Left _ <> b = b
      a      <> _ = a
    

    【讨论】: