Matt Navin
Matt Navin

Reputation: 11

Enum instance for tuples in Haskell

I'd like to define a tuple (x, y) as an instance of Enum class, knowing that both x and y are instances of Enum. A following try:

instance (Enum x, Enum y) => Enum (x, y) where
    toEnum = y
    enumFrom x = (x, x)

only results in error (y not in scope). I'm new to Haskell, could somebody explain how to declare such an instance?

Upvotes: 1

Views: 1185

Answers (2)

leftaroundabout
leftaroundabout

Reputation: 120711

Not a good idea if you ask me, but anyway —

To make an instance of a type class, you need to look at the signatures.

class Enum a where
  succ :: a -> a
  pred :: a -> a
  toEnum :: Int -> a
  fromEnum :: a -> Int
  enumFrom :: a -> [a]
  enumFromThen :: a -> a -> [a]
  enumFromTo :: a -> a -> [a]
  enumFromThenTo :: a -> a -> a -> [a]

So in your case

toEnum :: Int -> (x, y)

but toEnum = y isn't even defined, because y is just a type, not a value or constructor. Possibilities would be

toEnum n = (toEnum 0, toEnum n)

or

toEnum n = (toEnum n, toEnum n)

or

toEnum n = (toEnum $ n`div`2, toEnum $ (n+1)`div`2)

As for enumFrom, your version has signature

enumFrom :: a -> (a,a)

but we need

enumFrom :: (x,y) -> [(x,y)]

what definition is suitable depends on how toEnum was defined; for my first suggestion it would be

enumFrom (x,y) = [ (x,y') | y' <- enumFrom y ]

Reading Dietrich Epp's comment

It's not actually possible to create a useful Enum (x, y) from Enum x and Enum y. You'd need additional context, like Bounded x, Bounded y, Enum x, Enum y => Enum (x, y).

I thought about ways it could actually be done meaningfully. It seems possible sure enough, a bijection ℤ → ℤ2 exists. My suggestion:

[ ...
, (-3,-3), (-3,-2), (-2,-3), (-3,-1), (-1,-3), (-3,0), (0,-3), (-3,1), (1,-3), (-3,2), (2,-3), (-3,3), (3,-3)
, (-2,3), (3,-2), (-1,3), (3,-1)
, (-2,-2), (-2,-1), (-1,-2), (-2,0), (0,-2), (-2,1), (1,-2), (-2,2), (2,-2)
, (-1,2), (2,-1)
, (-1,-1), (-1,0), (0,-1), (-1,1), (1,-1)
, (0,0)
, (1,0), (0,1), (1,1)
, (2,0), (0,2), (2,1), (1,2), (2,2)
, (3,0), (0,3), (3,1), (1,3), (3,2), (2,3), (3,3)
, ... ]

Note that this reduces to a bijection ℕ → ℕ2 as well, which is important because some Enum instances don't go into the negative range and others do.

Implementation:

Let's make a plain (Int,Int) instance; it's easy to generalize that to your desired one. Also, I'll only treat the positive cases.

Observe that there are k^2 tuples between (0,0) and (excluding) (k,0). All other tuples (x,y) with max x y == k come directly after it. With that, we can define fromEnum:

fromEnum (x,y) = k^2  +  2*j  +  if permuted then 1 else 0
      where k = max x y
            j = min x y
            permuted = y>x

for toEnum, we need to find an inverse of this function, i.e. knowing fromEnum -> n we want to know the parametes. k is readily calculated as floor . sqrt $ fromIntegral n. j is obtained similarly, simply with div 2 of the remainder.

toEnum n =    let k = floor . sqrt $ fromIntegral n
                  (j, permdAdd) = (n-k^2) `divMod` 2
                  permute (x,y) | permdAdd>0  = (y,x)
                                | otherwise    = (x,y)
              in permute (k,j)

With fromEnum and toEnum, all the other functions are rather trivial.

Upvotes: 2

dave4420
dave4420

Reputation: 47052

instance (Enum x, Enum y) => Enum (x, y) where

In the above line, x and y are both types (type variables).

    toEnum = y
    enumFrom x = (x, x)

In the above two lines, x and y are both values ((value) variables). y-as-a-value has not been defined anywhere, that's what it not being in scope means.

As to how to declare such an instance, I'm not sure how you'd want fromEnum and toEnum to behave, for example.

Upvotes: 5

Related Questions