简体   繁体   中英

How to write a Semigroup Instance?

Problem

Given a data type, implement the Semigroup instance. Heres the data type I was given to implement: data Or ab = Fst a | Snd b deriving (Eq, Show, Num) data Or ab = Fst a | Snd b deriving (Eq, Show, Num) . And it should function like so:

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

When I test values like > Fst "help" <> Fst "me" it works correctly, but when I test other values I get errors. When I try to fix these errors by deriving the classes from the errors, I get more errors. What am I doing wrong here?

My Code

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  

Error

When I try to test with integers > Fst 1 <> Fst 2 I get:

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

When I try to derive the Num class data Or ab = Fst a | Snd b deriving (Eq, Show, Num) data Or ab = Fst a | Snd b deriving (Eq, Show, Num) I get:

    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.

The real problem is the constraint you've put on your instance, which you don't need. Just write

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

As chepner shows, you can actually cut down the number of lines and the number of patterns, and return one of the arguments as the result instead of constructing a new copy of it. That's potentially good for efficiency.

Less importantly, the parentheses in your patterns are all redundant, because application has higher precedence than any operator.

In your definition, there is no need for a or b to instances of any particular type class, because you never actually do anything with the wrapped values.

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

Since your Or type is isomorphic to Either , compare this to the Either instance in the semigroups package, which similarly places no constraints on the types involved.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM