最近,我想出了一个想法,即可以在 Haskell 中模拟“交叉类型”。具体来说,我的意思是“接口”的交集,因为它们通常是用 OOP 语言构思的。例如,对具有接口和交集类型的语言使用一些伪代码来了解我的意思:
interface HasName {
val name: String
}
interface HasAge {
val age: Int
}
-- An object of type HasName & HasAge can call the methods of both
type HasNameAndAge = HasName & HasAge
-- For example:
val myObject: HasNameAndAge = ...
val example = "My name is: ${myObject.name}. My age is ${myObject.age}"
我希望用 Haskell 类型类做类似的事情。我的方法是用* -> Constraint
Haskell 中的元素替换接口(例如,单参数类型类):
class HasName a where
name :: a -> String
class HasAge a where
age :: a -> Int
现在,给定这样的类型类,想法是表单exists a. C a => a
(where c :: * -> Constraint
)类型的元素对应于“接口”的实现C
。给定这样的标识,我们可以通过附加约束轻松构造非匿名交集类型,例如:
{-# LANGUAGE ExistentialQuantification, RankNTypes #-}
-- The intersection of HasName and HasAge:
data HasNameAndAge = forall a. HasNameAndAge ((HasName a, HasAge a) => a)
-- Example:
data Person = Person {
personName :: String,
personAge :: Int
}
instance HasName Person where
name = personName
instance HasAge Person where
age = personAge
myObject :: HasNameAndAge
myObject = HasNameAndAge (Person "Nathan" 27)
问题是,试图概括这一点并使其在[* -> Constraint]
“对象”实现的接口列表中通用,我无法让 GHC 推断出它需要什么才能使其正常工作。这是我最近的尝试:
{-# LANGUAGE
ConstraintKinds,
KindSignatures,
ExistentialQuantification,
DataKinds,
TypeFamilies,
TypeOperators,
RankNTypes,
AllowAmbiguousTypes,
TypeSynonymInstances,
FlexibleInstances,
MultiParamTypeClasses,
FlexibleContexts,
UndecidableInstances
#-}
import Data.Kind
class Elem (x :: * -> Constraint) (xs :: [* -> Constraint]) where
instance Elem x (x ': xs) where
instance Elem x xs => Elem x (y ': xs) where
type family All (cs :: [* -> Constraint]) a :: Constraint where
All '[] x = ()
All (c ': cs) x = (c x, All cs x)
-- The type of "objects" implementing all interfaces cs.
data AbstractType (cs :: [* -> Constraint])
= forall a. All cs a => AbstractType a
-- An example object of type HasName & HasAge.
myObject :: AbstractType '[HasName, HasAge]
myObject = AbstractType $ Person "Nathan" 27
-- Instances needed for every "interface" to get this to work.
instance Elem HasName cs => HasString (AbstractType cs) where
name (AbstractType x) = name x
instance Elem HasAge cs => HasAge (AbstractType cs) where
age (AbstractType x) = age x
-- Example:
myObject :: AbstractType '[HasName, HasAge]
myObject = AbstractType $ Person "Nathan" 27
example = do
print $ age myObject
putStrLn $ name myObject
看来我需要做更多的刺激才能让 GHC 在这里最后接受我想要的实例。当我尝试编译上述内容时,出现如下错误:
* Could not deduce (HasName a) arising from a use of `name'
from the context: Elem HasName cs
直观地说,只要是 in就HasName
应该成立,因为它是具有约束的存在类型。例如,但是,我不确定如何让 GHC 类型检查器相信这一事实。AbstractType cs
HasName
cs
AbstractType cs
All cs a
All '[HasName, HasAge] a = (HasName a, HasAge a)
我也收到如下错误:
* No instance for (Elem HasName '[HasName, HasAge])
arising from a use of `name'
因此,看来我的类型级别的实现elem
不正确,或者 GHC 无法测试 kind 术语之间的相等性* -> Constraint
,所以我不确定当前版本的 GHC 是否可行。