【问题标题】:List of items of types restricted by typeclass受 typeclass 限制的类型项目列表
【发布时间】:2011-06-08 20:41:11
【问题描述】:

我正在尝试对类型限制为某些类型类的实例的项目列表进行编码:

{-# LANGUAGE RankNTypes, TypeSynonymInstances, LiberalTypeSynonyms #-}
module Test where

class Someable a where
  some :: a -> String

data Some = Some String

type SomeGroup = forall a. Someable a => [a]

instance Someable Some where
  some (Some v) = v

instance Someable SomeGroup where
  some (x:xs) = (some x) ++ ", " ++ (some xs)

main = do
  putStrLn $ show.some [Some "A", [Some "B", Some "C"]]

但是编译失败并出现错误:

Test.hs:14:10:
    Illegal polymorphic or qualified type: SomeGroup
    In the instance declaration for `Someable SomeGroup'

似乎我什至没有为类型同义词定义实例......

我知道heterogenous collections wiki 文章,但想知道为什么我的方法不起作用——对我来说,通过将集合限制为仅包含具有某种类型实例的类型的项目来定义类型似乎很自然类。

【问题讨论】:

  • 您在这里尝试做的事情确实是有道理的,但是 Haskell 没有很好地支持它,并且通常是非惯用设计的症状。如果您对如何操作感到好奇,@hammar 将为您提供一个起点。如果您在实际代码中遇到这种情况,您最好重新考虑您的方法。

标签: haskell typeclass heterogeneous


【解决方案1】:

如果我理解正确,则需要有一个数据类型来包装存在,以便有一个地方来存储 type class dictionary 以及每个元素。

添加一些包装器使其工作:

{-# LANGUAGE ExistentialQuantification, TypeSynonymInstances #-}

module Test where

class Someable a where
  some :: a -> String

data Some = Some String

data SomeWrapper = forall a. Someable a => SomeWrapper a

type SomeGroup = [SomeWrapper]

instance Someable Some where
  some (Some v) = v

instance Someable SomeWrapper where
  some (SomeWrapper v) = some v

instance Someable SomeGroup where
  some (x:xs) = (some x) ++ ", " ++ (some xs)

main = do
  putStrLn $ some [SomeWrapper (Some "A"), SomeWrapper [SomeWrapper (Some "B"), SomeWrapper (Some "C")]]

当然,这有点难看。不幸的是,我不知道有什么更好的方法。

【讨论】:

  • 请注意,您定义的类型与问题中的类型不同:∀ x. C x => [x][∀ x. C x => x] 不同。
  • @camccann:您确定您正确阅读了我的代码吗? SomeWrapperforall x. C x => x,我使用 [SomeWrapper]
  • @andreypopp:是的,所以必须有一些额外的信息才能在运行时解决它。在 (GHC) Haskell 中,这是类型类字典。它可以类比为面向对象语言中的 vtable 指针。
  • 我不明白这条评论:“需要有一个数据类型来包装存在,以便有一个地方来存储类型类字典”。 GHC 与 C++ 不同,将类型类字典作为单独的参数传递给函数,SPJ 将其列为对 OO 语言的改进。优点之一是您不需要创建实际对象来调用“虚拟”函数,因此例如您可以在类型类字典中传递“C++ 构造函数”(创建对象的函数)。你能解释或澄清你的答案吗?
  • @user239558:是的,通常字典是分开传递的,但是当你有存在类型时这会改变。假设您有一个数据类型data Foo = forall a. Num a => a 和一个函数f (Foo x) = Foo (x + fromInteger 1)f 如何知道要使用哪个实例?它不能静态确定,因为我们知道实际类型的唯一地方是我们构造 Foo 值的时候。答案是必须将类型类字典添加为Foo 的额外隐藏字段,当我们进行模式匹配时可以使用。
【解决方案2】:

您还可以使用 GADT 进行烹饪。在某些情况下,这可能会稍微短一些,并且它明确了在模式匹配之后哪些类型的字典可用。

这是您的示例的一个轻微变体:

{-# LANGUAGE GADTs #-} 
class Someable a where
  some :: a -> String

instance Someable Int where
  some n = show n

data SomeString = SomeString String
instance Someable SomeString where
  some (SomeString s) = s

data SomeGroup where 
    Nil :: SomeGroup
    Cons :: Someable a => a -> SomeGroup -> SomeGroup

instance Someable SomeGroup where
    some Nil = ""
    some (Cons x Nil) = some x
    some (Cons x xs) = some x ++ ", " ++ some xs

list = Cons (3::Int) (Cons (SomeString "abc") (Cons (42::Int) Nil))
main = print . some $ list

几个小笔记:

  • 您忘记了递归的基本情况:)
  • putStrLn . showprint 相同。
  • 您必须将数字的类型显式声明为Int,因为整数文字被特殊处理,即42 被转换为fromInteger 42 类型的Num a => a。直接构建列表时有点笨拙,但大多数时候它会更顺利地工作。
  • 你当然可以为Cons-ing 元素定义你自己的语法糖到一个列表中,但是语法永远不会像 Haskell 的内置语法那样好!

主要的一点是你失去了所有标准列表函数的使用。当我的列表处理需求非常有限时,我通常会使用这种解决方案;另一方面,一个折叠的写入速度足够快......虽然你的里程会有所不同,但我不知道你真正想要使用这个列表用于什么。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多