Jace Cole
Jace Cole

Reputation: 21

Functions with higher kinds?

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

Answers (2)

leftaroundabout
leftaroundabout

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

Benjamin Hodgson
Benjamin Hodgson

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 as in. Likewise, there's no way to get an a out of a Proxy a, because there aren't any as 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

Related Questions