一个没有存在类型的异构索引结构?

时间:2012-10-27 06:35:54

标签: haskell existential-type

我正在尝试构建异构索引结构,并提出了以下解决方案I was told not to use existential types

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

我想保持接口定义(typeclass)与具体实现(datainstance)之间的分离。 编辑关注@ hammar的评论:在实际应用中,值不是Show n,而只是存储了一个查询;另外还有myData更复杂的记录。

如果这可以带来更好的解决方案,那么确切的要求是构建地图地图(内部地图)。每个内部地图都是同质的,并且形式为Map String a但是每个内部地图可以为其值强制执行不同的类型。您可能还将其视为两级索引结构。实现不必使用Data.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 

3 个答案:

答案 0 :(得分:6)

这是面向对象语言的一个很好的模式,但它是一个众所周知的Haskell antipattern。 阅读那篇文章。我想让你读到比我说的更多的东西。

另见this answer,但条件是我认为GADT比存在类型更优雅(见下文)。


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

(想象一下Craig,一个刚接触Java的C程序员,他试图找到一个指向结构数组的指针,或者在尝试使用malloc的功能制作方法时遇到困难,或因为没有指针而感到沮丧算术.Janet,Java程序员会回答问Craig他为什么要自己做指针,垃圾收集有什么问题,以及为什么当他们有内置边界检查的数组时,任何人都想要指针运算?真的克雷格最好先学习编写惯用Java,然后再决定C中哪些功能真的离不开.OO是C的一种不同的范例 - 尽可能与机器相关 - 同时仍然是相对的 - 机器独立的哲学。克雷格应该把新范式作为第一要务来学习。这可能会让他成为一个更好的C程序员。不要去法国只会说英语,看CNN和吃麦当劳!你应该有一种感觉试着写你的c尽可能地发挥作用。)


如果确实希望没有关于您的数据的其他信息,而不是它遵守您的合同,一种方法是使用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对您的数据做出 no 假设。它是参数多态,适用于任何事物。任何东西,甚至是功能。它所做的唯一假设是您的密钥是可排序的(Ord k =>),并且您的数据是同质的(尽管我们的GADT是异构的,但它是由异构数据构成的)。


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

答案 1 :(得分: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

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

然后,您可以使用Dynamic将数据投射到toDyn类型,并使用DynamicfromDynamic类型安全地投射数据。


虽然这是一种非常有效的方法,但我和其他许多Haskeller强烈建议您考虑制作自定义数据类型,而不是诉诸真正的异构集合。假设(在万圣节的精神中)你知道你将在这张地图中添加的唯一事物是Cat s,Witch es和Ghoul s。< / p>

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 ...

答案 2 :(得分:0)

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

type MyMap = Map String String

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

这可以很容易地用于其他类型类。

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

相关问题