4

Problem

Given a data type, implement the Semigroup instance. Heres the data type I was given to implement: data Or a b = 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 a b = 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.
Jonathan Portorreal
  • 2,730
  • 4
  • 21
  • 38

2 Answers2

6

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.

dfeuer
  • 48,079
  • 5
  • 63
  • 167
  • Is the compiler smart enough to recognize when `Snd a` is being reused unmodified to avoid constructing a new copy? I wasn't being clever; I just copied the definition verbatim as a published example of a similar instance. :) – chepner Aug 30 '16 at 16:57
  • @chepner, I'm not sure. That doesn't necessarily happen in core-to-core, as I recall, and I'm much less familiar with what goes on in STG and beyond. – dfeuer Aug 30 '16 at 17:08
4

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
chepner
  • 497,756
  • 71
  • 530
  • 681