从异构列表中获取值

时间:2014-03-30 17:54:11

标签: haskell typeclass heterogeneous

我试图创建一个属于特定类型类的异构值列表。在我的例子中,类型类是一个具有功能依赖性的多参数类型类,但为了简单起见,我将在这里使用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

有没有办法可以拥有属于类型类的值列表,并且能够从列表中取出一个值?

4 个答案:

答案 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。将TypeOperatorsEmptyDataDecls添加到我们的扩展程序中:

{-# 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也无法做更多事情。