Reputation: 21
Suppose the following data types are defined:
data X a = X {getX :: a}
data Y a = Y {getY :: a}
data Z a = Z {getZ :: a}
Must there be three separate functions, getX
, getY
, and getZ
? It seems to me that there could be a function defined something like this:
get :: forall (τ :: (* -> *)) (a :: *). τ a -> a
get (_ x) = x
Obviously this is not valid standard Haskell, but there are so many extensions to GHC that seem like they might have a solution (RankNTypes
,ExistentialQuantification
,DataKinds
,etc.). Besides the simple reason of avoiding a tiny amount of typing, there is the benefit of avoiding the namespace pollution that the record solution creates. I suppose this is really just a more implicit solution than using a type class like this:
class Get f where
get :: f a -> a
However, it appears that defining a generic function would be more useful than a type class, because the fact that it is implicitly defined means it could be used in many more places, in the same way that ($)
or (.)
is used. So my question has three parts: is there a way to accomplish this, is it a good idea, and if not, what is a better way?
Upvotes: 2
Views: 124
Reputation: 120711
Well, that unconstrained generic type of get
certainly can't work. This would also allow you to extract, say, a Void
value from Const () :: Const () Void
.
You can however obtain a suitably constrained version of this function quite simply with generics. You still need a type class, but not need to define instances in the traditional sense. It ultimately looks like this:
{-# LANGUAGE TypeFamilies, DeriveGeneric, DeriveAnyClass #-}
import GHC.Generics
class Get τ where
get :: τ a -> a
data X a = X a deriving (Generic1, Get)
data Y a = Y a deriving (Generic1, Get)
data Z a = Z a deriving (Generic1, Get)
To actually get this to work, we only need two weird representation-type instances:
instance Get f => Get (M1 i t f) where get = get . unM1
instance Get Par1 where get = unPar1
Now the actual implementation for X
, Y
and Z
can just use a default signature and reduce the extraction to the underlying type-representation. To this end, define the class thus:
{-# LANGUAGE DefaultSignatures #-}
class Get τ where
get :: τ a -> a
default get :: (Generic1 τ, Get (Rep1 τ)) => τ a -> a
get = get . from1
Upvotes: 3
Reputation: 44603
How about this type?
newtype Pred a = Pred (a -> Bool)
Or this one?
data Proxy a = Proxy
There's no way to get an a
out of a Pred a
. You can only put a
s in. Likewise, there's no way to get an a
out of a Proxy a
, because there aren't any a
s inside it.
So a function get :: forall f a. f a -> a
can't exist in general. You need to use a type class to distinguish between those types f
from which you can extract an a
and those from which you can't.
Upvotes: 10