假设定义了以下数据类型:
data X a = X {getX :: a}
data Y a = Y {getY :: a}
data Z a = Z {getZ :: a}
必须有三个单独的函数getX
,getY
和getZ
吗?在我看来,可能有一个定义如下的函数:
get :: forall (τ :: (* -> *)) (a :: *). τ a -> a
get (_ x) = x
显然这不是有效的标准Haskell,但GHC有很多扩展,似乎可能有解决方案(RankNTypes
,ExistentialQuantification
,DataKinds
等。除了避免少量键入的简单原因之外,还有避免记录解决方案创建的命名空间污染的好处。我想这实际上只是一个比使用这样的类型类更隐式的解决方案:
class Get f where
get :: f a -> a
然而,似乎定义泛型函数比类型类更有用,因为它是隐式定义的事实意味着它可以在更多的地方使用,就像($)
或使用(.)
。所以我的问题有三个部分:有没有办法实现这个目标,这是一个好主意,如果没有,那么更好的方法是什么?
答案 0 :(得分:10)
这种类型怎么样?
newtype Pred a = Pred (a -> Bool)
还是这个?
data Proxy a = Proxy
无法从a
中获得Pred a
。您只能将a
放进去。同样,也无法从a
中获取Proxy a
,因为其中没有任何a
。< / p>
因此,函数get :: forall f a. f a -> a
通常不存在。您需要使用类型类来区分可以从中提取f
的那些类型a
和不能从中提取的类型。
答案 1 :(得分:3)
那么,get
这种不受约束的泛型类型当然不行。这也允许您从Const () :: Const () Void
中提取Void
值。
然而,您可以使用generics非常简单地获得此函数的适当约束版本。您仍然需要一个类型类,但不需要定义传统意义上的实例。它最终看起来像这样:
{-# 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)
要真正实现这一点,我们只需要两个奇怪的表示类型实例:
instance Get f => Get (M1 i t f) where get = get . unM1
instance Get Par1 where get = unPar1
现在X
,Y
和Z
的实际实现只能使用默认签名并将提取减少到基础类型表示。为此,定义类:
{-# LANGUAGE DefaultSignatures #-}
class Get τ where
get :: τ a -> a
default get :: (Generic1 τ, Get (Rep1 τ)) => τ a -> a
get = get . from1