简体   繁体   中英

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?

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.

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 xy == 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, ie knowing fromEnum -> n we want to know the parametes. k is readily calculated as floor . sqrt $ fromIntegral n 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.

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