我有两个记录,都有一个我想提取显示的字段.我如何安排事情,以便可以使用相同的功能操作它们?因为他们有不同的领域(在这种情况下,firstName和buildingName)是他们的名字领域,他们每个人都需要一些"适配器"代码映射firstName到name.这是我到目前为止:
class Nameable a where
  name :: a -> String
data Human = Human {
  firstName :: String
}
data Building = Building {
  buildingName :: String
}
instance Nameable Human where
  name x = firstName x
instance Nameable Building where
  -- I think the x is redundant here, i.e the following should work:
  -- name = buildingName
  name x = buildingName x
main :: IO ()
main = do
  putStr $ show (map name items)
  where
    items :: (Nameable a) => [a]
    items = [ Human{firstName = "Don"}
            -- Ideally I want the next line in the array too, but that gives an 
            -- obvious type error at the moment.
            --, Building{buildingName = "Empire State"}
            ]
这不编译:
TypeTest.hs:23:14:
    Couldn't match expected type `a' against inferred type `Human'
      `a' is a rigid type variable bound by
          the type signature for `items' at TypeTest.hs:22:23
    In the expression: Human {firstName = "Don"}
    In the expression: [Human {firstName = "Don"}]
    In the definition of `items': items = [Human {firstName = "Don"}]
我原以为这instance Nameable Human部分会让这项工作成功.有人可以解释我做错了什么,以及奖励积分我试图开始工作的"概念",因为我无法知道要搜索什么.
这个问题感觉很相似,但我无法弄清楚与我的问题的联系.
ham*_*mar 13
考虑以下类型items:
items :: (Nameable a) => [a] 
它说,对于任何Nameable类型,items将给我一个该类型的列表.它并没有说items的是,可能包含不同的列表Nameable类型,因为你的想象.你需要类似的东西items :: [exists a. Nameable a => a],除了你需要引入一个包装类型并使用它forall.(见:存在类型)
{-# LANGUAGE ExistentialQuantification #-} 
data SomeNameable = forall a. Nameable a => SomeNameable a 
[...]
items :: [SomeNameable]
items = [ SomeNameable $ Human {firstName = "Don"},
          SomeNameable $ Building {buildingName = "Empire State"} ]
数据构造函数中的量词SomeNameable基本上允许它忘记所使用的所有内容a,除了它Nameable.因此,您只能Nameable在元素上使用类中的函数.
为了使这个更好用,你可以为包装器创建一个实例:
instance Nameable (SomeNameable a) where
    name (SomeNameable x) = name x
现在您可以像这样使用它:
Main> map name items
["Don", "Empire State"]
每个人都在寻求存在量化或代数数据类型.但这些都是矫枉过正(取决于你的需求,ADT可能不是).
首先要注意的是Haskell没有向下转型.也就是说,如果您使用以下存在性:
data SomeNameable = forall a. Nameable a => SomeNameable a
然后当你创建一个对象
foo :: SomeNameable
foo = SomeNameable $ Human { firstName = "John" }
关于该对象使用哪种具体类型(此处Human)的信息永远丢失.我们知道的唯一事情是:它是某种类型a,并且有一个Nameable a实例.
这样一对有什么可能呢?那么,你可以得到name的a你有,而且......就是这样.这里的所有都是它的.事实上,有一种同构.我将创建一个新的数据类型,以便您可以看到在所有具体对象具有比类更多的结构的情况下如何出现这种同构.
data ProtoNameable = ProtoNameable {
   -- one field for each typeclass method
    protoName :: String
}
instance Nameable ProtoNameable where
    name = protoName
toProto :: SomeNameable -> ProtoNameable
toProto (SomeNameable x) = ProtoNameable { protoName = name x }
fromProto :: ProtoNameable -> SomeNameable
fromProto = SomeNameable
我们可以看到,这个奇特的生存型SomeNameable具有相同的结构和信息ProtoNameable,这是同构的String,因此,当你使用这个崇高的理念SomeNameable,你真的只是说String在一个令人费解的方式.那么为什么不说呢String?
您的items定义与此定义具有完全相同的信息:
items = [ "Don", "Empire State" ]
我应该添加一些关于这种"原型化"的注释:当你存在量化的类型类具有某种结构时,它就像这样直截了当:即它看起来像一个OO类.
class Foo a where
    method1 :: ... -> a -> ...
    method2 :: ... -> a -> ...
    ...
也就是说,每种方法仅使用a一次作为参数.如果你有类似的东西Num
class Num a where
    (+) :: a -> a -> a
    ...
它a在多个参数位置使用,或者因此消除存在性并不容易,但仍然可能.然而,由于两种表征的复杂性和远距离关系,我的建议是从挫折变为微妙的依赖于上下文的选择.然而,每当我看到实践中使用的存在性时,它就是Foo那种类型的tyepclass,它只会增加不必要的复杂性,所以我非常强调它将它视为反模式.在大多数情况下,我建议从代码库中删除整个类,并专门使用protoized类型(在给它一个好名字后).
此外,如果你确实需要垂头丧气,那么存在不是你的男人.你可以使用代数数据类型,就像其他人已经回答的那样,或者你可以使用Data.Dynamic(它基本上是一个存在性而不是Typeable.但是不要这样做;一个Haskell程序员诉诸于Dynamicungentlemanlike.一个ADT是方法去哪里,你可以在一个地方描述所有可能的类型(这是必要的,以便执行"向下转换"的功能知道他们处理所有可能的情况).
我喜欢@ hammar的答案,你也应该查看这篇提供另一个例子的文章.
但是,您可能想要对您的类型有不同的看法.的拳击Nameable进入SomeNameable数据类型通常让我开始思考一个联合类型的具体情况是否有意义.
data Entity = H Human | B Building
instance Nameable Entity where ...
items = [H (Human "Don"), B (Building "Town Hall")]
我不确定为什么要使用相同的函数来获取a的名称和a Human的名称Building.
如果他们的名字以完全不同的方式使用,除了打印它们等简单的东西,那么你可能想要两种不同的功能.类型系统将自动引导您选择在每种情况下使用的正确功能.
但是如果有一个名字对你的程序的整个目的有重要意义,并且就你的程序而言a Human和a Building
在这方面实际上几乎是一样的,那么你可以在一起定义它们的类型:
data NameableThing =
  Human { name :: String } |
  Building { name :: String }
这为您提供了一个多态函数name,可以适用于NameableThing您遇到的任何特定风格,而无需进入类型类.
通常,您将使用类型类来处理不同类型的情况:如果您有某种非平凡的操作,它们具有相同的目的,但对于多种不同的类型具有不同的实现.即便如此,使用其他方法通常更好,比如将函数作为参数传递("高阶函数"或"HOF").
Haskell类型类是一个漂亮而强大的工具,但它们与面向对象语言中所谓的"类"完全不同,并且它们的使用频率较低.
我当然不建议通过使用Haskell的高级扩展(如存在资格)来使您的程序复杂化,以适应面向对象的设计模式.