Reputation: 51
I have this task in which I have to define 2 typeclasses in Haskell. One is simple - it implements Ord and has one method which converts given type to an int. Something like this:
class (Ord a) => Id a where
toInt :: a -> Int
But then I have another typeclass HasId which has a method getId. This method should return Id typeclass. So I wrote something like this:
class HasId a where
getId :: a -> Id a
And I get an error I don't know how to solve.
• Expected kind ‘* -> Constraint’, but ‘Int’ has kind ‘*’
• In the class declaration for ‘HasId’
|
| class (Int a) => HasId a where | ^^^^^
• Expected a type, but ‘Id a’ has kind ‘Constraint’
• In the type signature: getId :: a -> Id a
In the class declaration for ‘HasId’
|
| getId :: a -> Id a |
Can somebody tell me how can I return typeclass in another's typeclass method? Should I implement some instance of Id typeclass at first?
Upvotes: 0
Views: 575
Reputation: 120711
It's important to understand that a Haskell class
is very different from a class in OO languages. It's a type class, i.e. it “groups together a collection of types”, whereas an OO class groups together a collection of values. IOW, an OO class is a type (containing values), but a Haskell class is rather something like a type of types, as it were.
Now, a function/method always takes values as argument and gives back values as results. But it can't give you back a type, nor a “value of a typeclass” because there's no such thing.
Instead, the way to use type classes is this: you write a polymorphic function, i.e. a function that accepts or yields some value of unspecified type. That's what type variables are there for. Normally, a polymorphic signature means that the function can deal with values of any type, like with
length :: [a] -> Int
which takes a list and doesn't care what type its elements have. But then you say that you do care a bit what types are used, namely, you require that they are in the class. That's a constraint, and it's written with the =>
notation. toInt
actually has the signature (automatically generated by the class declaration)
toInt :: Id a => a -> Int
Frankly, I doubt that this is really what you want. If all you can do with an Id a
is converting it to an Int
, then the result effectively is an Int
(just with a type-annotation which indicates what kind of object this Id belongs to), regardless of the a
parameter. So instead of the Id
class, you should consider having an Id
type
newtype Id' a = Id {toInt :: Int} -- The prime ' symbol has no particular meaning,
-- I just use it for disambiguation.
Then, you have the more sensible type
toInt :: Id' a -> Int
without needing any constraints.
newtype
is much more similar to a OO class, in that it actually defines a concrete type which has values you can pass around.
The getId
method is also a constrained-polymorphic function, that can take something which “has an Id
” and gives you back that Id. In this case, a class makes sense (because you can have different data structure that may store their Ids in different ways). This would now actually be
class HasId a where
getId :: a -> Id' a
As per Welperooni's answer, it is indeed also conveivable to define HasId
using your original Id
class, just it doesn't really make much sense. Id
is the class of all types which can be used as identifyers. You could express that getId
can yield an Id of any such type:
class HasId a where
getId :: Id b => a -> b
Note that the result-Id type is now completely independent of the type of object whose Id you want to calculate. b
can always be any Id-type. As I said this doesn't make sense: why would you need a whole lot of different types that represent Ids, but require all the HasId
types to support all of them?
In fact this cannot be implemented as it stands, because there's no mechanism that allows you to generate an Id of arbitrary Id
type. This would require an additional method
class Id a where
toInt :: a -> Int
fromInt :: Int -> a
and now you could do
class HasId a where
getId :: Id b => a -> b
data SomeObj { objName :: String
, objId :: Int }
instance HasId SomeObj where
getId (SomeObj _ i) = fromInt i
However, if you require that any Id
type is convertible both from and to Int
, then these types must be isomorphic to Int
itself. Therefore, the newtype Id' a
approach is almost certainly better.
Upvotes: 7
Reputation: 643
You declared the return value to be a HKT Id a
, but what you wanted is another type, constrained by the typeclass Id
.
What you need is not getId :: a -> Id a
, but getId :: Id b => a -> b
Upvotes: 1