Reputation: 11
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
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)
fromEnum x
andEnum y
. You'd need additional context, likeBounded 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.
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
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