简体   繁体   中英

Is there a way to refer directly to typeclass instances in Haskell?

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?

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.

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?

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