简体   繁体   中英

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?

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. fa -> a get :: forall f a. fa -> 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.

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

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