Reputation: 13088
The benefit of this could be to store certain metadata about the type in a canonical location. Sometimes, it isn't convenient to have a value of the type before using some instance methods on it; For instance if I have:
class Foo a where
foo :: String
foo = "Foo"
This is not actually valid Haskell. Instead it seems I have to have something like:
class Foo a where
foo :: a -> String
foo = const "Foo"
and now foo
will actually have type Foo a => a -> String
, but I would actually like to be able to do something like having a foo
with type Instance Foo -> String
. For this to be useful in some contexts, it might be also necessary to iterate over all (in-scope?) instances, or in other contexts, to be able to specifically materialize an instance for a given type.
I guess the issue is that instances and typeclasses are not first-class entities in Haskell?
Upvotes: 2
Views: 139
Reputation: 80754
The "old school" way of doing it is providing a "dummy" parameter whose purpose is nothing but helping the compiler find the appropriate instance. In this world, your class would look something like:
data Dummy a = Dummy
class Foo a where
foo :: Dummy a -> String
-- usage:
boolFoo = foo (Dummy :: Dummy Bool)
In fact, this trick was so ubiquitous that the Dummy
type was semi-standardized as Data.Proxy
.
But in modern GHC there is a better way: TypeApplications
.
With this extension enabled, you can just straight up specify the type when calling the class method:
class Foo a where
foo :: String
boolFoo = foo @Bool
(this doesn't only work for class methods; it will work with any generic function, but be careful with the order of type parameters!)
You may also need to enable AllowAmbiguousTypes
in order to declare such class. Though I'm not sure I remember this correctly, and I don't have a computer handy to check.
Upvotes: 7
Reputation: 60463
The old way (which I still prefer) is to use a proxy.
import Data.Proxy
class Foo a where
foo :: Proxy a -> String
instance Foo FancyPants where
foo _ = "a fancypants instance"
fooString :: String
fooString = foo (Proxy :: Proxy FancyPants)
So we didn't actually need a value of type FancyPants
to use foo
, all we needed is a value of Proxy FancyPants
-- but you can create proxies of any type you want. This can be done in a polymorphic context too; usually it requires the use of the ScopedTypeVariables
extension.
The new way is to use the TypeApplications
and AllowAmbiguousTypes
extension:
{-# LANGUAGE TypeApplications, AllowAmbiguousTypes #-}
class Foo a where
foo :: String
instance Foo FancyPants where
foo = "a fancypants instance"
fooString :: String
fooString = foo @FancyPants
Which looks nicer, but working with it in practice tends to be more irritating for a reason I can't quite put my finger on.
Did that answer the question?
Upvotes: 4