5

我正在尝试构建异构索引结构并提出以下解决方案,但有人告诉我不要使用存在类型

你能看到更好的解决方案吗?

我想保持接口定义(typeand class)和具体实现(dataand instance)之间的分离。按照@hammar 的评论进行编辑Show:在实际应用程序中,值不是n 而是简单地存储一个查询;附加记录也myData更复杂。

如果这可以带来更好的解决方案,那么确切的要求是构建地图的地图(内部地图)。每个内部映射都是同质的,Map String a但是每个内部映射可以对其值强制使用不同的类型。您也可以将其视为两级索引结构。实现不必使用 aData.Map但必须是高效的。

{-# LANGUAGE ExistentialQuantification #-}
module Scratch.SO_ExtistentialTypes where

import Data.Map

data HeteroValue = forall a. Show a => HV a 

instance Show HeteroValue where
    show (HV b) = show b

type MyMap = Map String HeteroValue

class MyClass c where 
    getMyMap :: c -> MyMap

data MyData = MyData {
    myMap ::  MyMap
}

instance MyClass MyData where
    getMyMap = myMap

这个片段可以使用 ghci 运行

let myMap = fromList [("key1", HV "abc"), ("key2", HV 123)] :: MyMap
let myData = MyData myMap
getMyMap myData 
4

3 回答 3

6

这是面向对象语言的一个很好的模式,但它是众所周知的Haskell 反模式。阅读那篇文章。我想让你读的比我说的任何其他内容都多。

另请参阅此答案,但附带条件是我认为 GADT 比存在类型更优雅(见下文)。


请尝试找到编写程序的最佳函数式编程方式,而不是重新实现面向对象编程的最佳函数式编程方式。除了希望以 OO 风格进行编程之外,您还没有明确代码的任何目的。

这可能会让他成为一个更好的 C 程序员。不要去法国只说英语,看CNN和吃麦当劳!从某种意义上说,您应该尝试以尽可能实用的方式编写代码。)


如果您真的不想获得关于您的数据的其他信息,而不是它遵守您的合同,那么一种方法是使用 GADT。您应该知道 Haskell 会要求您坚持索赔;没有演员可以让你摆脱一个轻率的设计决定。(强制转换是一种将编译时检查转变为运行时检查的方法,或者换一种说法,一种将编译时错误转变为运行时错误的方法。我不认为这是一件好事。)

{-# LANGUAGE GADTs #-}

class Contract a where
   toString :: a -> String
   fromInts :: Int -> Int -> a
   alter :: a -> a
   -- other functionality

data Encapsulated where
   Encapsulate :: Contract a => a -> Encapsulated

现在,一旦你封装了你的数据,你就可以对它做任何你喜欢的事情,就好像它是一个普通的数据类型一样,并恢复任何Contract像这样的 ed 功能:

munged :: Encapsulated -> String
munged (Encapsulate a) = toString.alter.alter.alter $ a

如果您愿意,您可以在地图中存储一大堆Encapsulated数据,无需Data.Map为您的存在做任何特殊或重新实现,因为这是强大的功能范式:对您的数据Data.Map不做任何假设。它是参数多态性,适用于任何东西。任何东西,甚至功能。它所做的唯一假设是您的键是可排序的 ( Ord k =>) 并且您的数据是同质的(我们的 GADT 是同质的,尽管是由异构数据制成的)。


这是满足您要求的一种方式,但如果我们知道您想要它的目的,我们可以为您提供更好的建议。(也许是另一个新问题!)请认真阅读我链接到的文章,看看您是否可以将您的类实现为一个充满函数/结果的数据类型,以及您的实例作为该数据类型的函数。

于 2012-10-28T09:28:12.587 回答
4

进行“异构集合”的一种方法是使用Data.Dynamic

module Scratch.SO_Dyn where

import Data.Dynamic
import Data.Map

type MyMap = Map String Dynamic

class MyClass c where 
    getMyMap :: c -> MyMap

data MyData = MyData {
    myMap ::  MyMap
}

instance MyClass MyData where
    getMyMap = myMap

您希望放入此地图的数据必须派生 Typeable。
使用{-# LANGUAGE DeriveDataTypeable #-}and deriving (Data, Typeable),另见http://www.haskell.org/ghc/docs/7.6.1/html/users_guide/deriving.html#deriving-typeable

然后,您可以将数据转换为Dynamicwith 的类型toDyn,并安全地将其从Dynamicwith 的类型转换fromDynamic


尽管这是一种完全有效的方法,但我和许多其他 Haskeller 强烈建议您考虑制作自定义数据类型,而不是诉诸真正的异构集合。假设(本着万圣节的精神)您知道一个事实,即您将放入这张地图的唯一种类的东西是Cats、Witches 和Ghouls。

data Cat = ...
data Witch = ...
data Ghoul = ...

通过简单地标记每个可能的选项,您可以稍后确定每个事物是什么。

data HeteroValue
  = DarkOmen Cat
  | Hag Witch
  | Haunting Ghoul

case (Map.lookup "Midnight visitor" theMap) of
  Just (DarkOmen cat) -> hiss cat
  Just (Hag witch) -> cackle witch
  Just (Haunting ghoul) -> spook ghoul
  Nothing -> error ...
于 2012-10-27T20:44:21.927 回答
0

显然,您可以对 a 做的唯一事情HeteroValue就是show将其转换为String. 考虑到这一点,存储值没有意义,您也可以只存储转换后的字符串:

type MyMap = Map String String

或者

data HeteroData = HD { getShow :: String }
type MyMap = Map String HeteroData

这很容易被其他类型类采用。

如果您改为在 上执行模式匹配之类的操作HeteroValue,则存在类型是一个很好的解决方案。

于 2012-10-28T01:50:28.430 回答