我试图创建一个属于特定类型类的异构值列表。在我的例子中,类型类是一个具有功能依赖性的多参数类型类,但为了简单起见,我将在这里使用Show类型类作为示例(使用GADT):
data ShowList :: * where
Nil :: ShowList
(:::) :: Show a => a -> ShowList -> ShowList
这很好用,我可以开始在ShowList中放置值
True ::: (3,4) ::: "abc" ::: 3 ::: Nil
我可以编写许多标准列表函数,例如map,filter,reverse,append等,没有问题,但只要我尝试取出一个元素,
head (x ::: xs) = x
我得错了
Could not deduce (t ~ a)
from the context (Show a)
bound by a pattern with constructor
::: :: forall a. Show a => a -> ShowList -> ShowList,
in an equation for `head'
at <interactive>:34:12-19
`t' is a rigid type variable bound by
the inferred type of head :: ShowList -> t at <interactive>:34:5
`a' is a rigid type variable bound by
a pattern with constructor
::: :: forall a. Show a => a -> ShowList -> ShowList,
in an equation for `head'
at <interactive>:34:12
In the expression: x
In an equation for `head': head (x ::: xs) = x
此错误有意义,因为head
的类型必须是Show a => ShowList -> a
,这不是一个合理的类型。
在这个示例情况下,存储一个字符串列表是可行的,而不是存储Show a
的列表,因为对{的实例唯一可以完成的事情是可行的。 {1}}应用Show
并获取字符串。
然而,在我的情况下,我定义的类型类更复杂一些:
show
所以我不能只存储typeclass Playable a b | a -> b where
play :: a -> Int -> b
而不是play x
。
有没有办法可以拥有属于类型类的值列表,并且能够从列表中取出一个值?
答案 0 :(得分:2)
看看HList。
作为初步,请考虑以下hack使head
工作:
data ShowList :: * -> * where
Nil :: ShowList z
(:::) :: Show a => a -> ShowList b -> ShowList a
head :: ShowList a -> a
head (x ::: xs) = x
我们将元素的类型存储在ShowList的类型中,我们可以在head
的签名中绑定它,并且GHC满足。别介意空z
,这只是一个例子!
*Main> Main.head $ "five hundred" ::: (123 ::: (True ::: Nil))
"five hundred"
...但现在您的错误已移至tail
:
tail :: ShowList a -> ShowList b
tail (x ::: xs) = xs
Could not deduce (b1 ~ b)
from the context (Show a)
bound by a pattern with constructor
::: :: forall a b. Show a => a -> ShowList b -> ShowList a,
in an equation for `tail'
每个:::
创建一个按其头部类型参数化的ShowList,使得很难达到尾部的类型。因此,我们无法访问b
,我们无法告诉GHC尾部应该是ShowList b
类型。
解决这个问题的方法(我知道,无论如何)是通过类型级别列表来参数化ShowList
。将TypeOperators
和EmptyDataDecls
添加到我们的扩展程序中:
{-# LANGUAGE GADTs, KindSignatures, TypeOperators, EmptyDataDecls #-}
data Empty
instance Show Empty where
show = "Empty"
type a :+: b = (a, b)
data ShowList :: * -> * where
Nil :: ShowList Empty
(:::) :: Show a => a -> ShowList b -> ShowList (a :+: b)
head :: ShowList (a :+: b) -> a
head (x ::: xs) = x
tail :: ShowList (a :+: b) -> ShowList b
tail (x ::: xs) = xs
这个有效!现在我们保留HList中每对配对的类型对。
*Main> head . tail $ "abcde" ::: (True ::: Nil)
True
答案 1 :(得分:2)
您的ShowList
类型被称为“存在主义”类型,大致意味着给定ShowList
类型的值存在其某些类型a
(列表元素,在您的情况下)。
关键是对此类型a
一无所知,除了它属于类型类Show
这一事实。
因此,head
等函数不能轻易输入
head :: ShowList -> ???
head (x ::: _) = x
使用非Haskell语法,类型为
head :: ShowList -> (exists a. Show a => a)
head (x ::: _) = x
但是在Haskell中不允许这样做。
主要思想是这样:Haskell不会让你返回头部,因为那是不可分类的,但是Haskell确实让你抓住头并用它来构建别的东西(必须有一个有效的类型)。例如
foo :: ShowList -> String
foo Nil = "Nil"
foo (x ::: _) = show x
没关系:我们采取行动,我们使用它来构建String
类型的值。我们也可以将x
放回另一个存在类型
data Showable :: * where
S :: Show a => a -> Showable
foo :: ShowList -> Showable
foo [] = error "unexpected Nil"
foo (x ::: _) = S x
我们还可以要求foo
的用户指定应将哪个功能应用于x
。
{-# LANGUAGE RankNTypes #-}
foo :: ShowList -> (forall a. Show a => a -> r) -> r
foo [] _ = error ""unexpected Nil"
foo (x ::: _) f = f x
test :: Int
test = foo (123 ::: ("aa" ::: Nil)) f
where f :: Show a => a -> Int
f = length . show
此处f
参数的类型很复杂。它指定f
必须适用于a
个班级Show
。即,类型a
由函数foo
选择,而不是f
:因此,f
需要在a
中具有多态性。
ShowList
类型几乎等于[Showable]
。使用[Showable]
可以使用标准列表功能,例如head
,而不会出现任何问题。缺点是它需要对数据添加/删除构造函数S
进行装箱/取消装箱。
答案 2 :(得分:1)
这种经典类型类反模式的更好替代方法是使用简单的数据类型。您的Playable
班级变为
newtype Playable a b = Playable { play :: a -> Int -> b }
现在您可以轻松处理[Playable a b]
并且仍然拥有类型类版本的所有功能(至少与您的问题中提到的功能一样多)。
Playable
形成一个monad:
instance Functor (Playable a) where
fmap f (Playable k) = Playable (\a -> f . k a)
instance Monad (Playable a) where
return b = Playable (\_ _ -> b)
Playable k >>= f = Playable (\a i -> play (f (k a i)) a i)
答案 3 :(得分:1)
您的ShowList
基本上是存在量化值的列表。你可以把它同形地写成:
data Showy = forall a. Show a => Showy a
-- or in GADT syntax:
data Showy' where
Showy' :: Show a => a -> Showy'
type ShowList' = [Showy]
除了ShowList
个实例之外,您的Show
对这些值一无所知;甚至没有一个参数化的类型变量。如果将值包装在类似于ShowList
的结构中但只包含单个元素,则可以从列表中取值。 Showy
恰好是这样的数据类型:
head' :: ShowList -> Showy
head' (x ::: xs) = Showy x
当然除了显示Showy
之外,您无法执行任何操作,但使用ShowList
也无法做更多事情。