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

Bru*_*der 4 haskell existential-type

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

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

我想保持接口定义(typeclass)与具体实现(datainstance)之间的分离.编辑以下@ hammar的评论:在实际应用程序中,值不是Shown,而是简单地存储查询; 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
Run Code Online (Sandbox Code Playgroud)

可以使用ghci运行此代码段

let myMap = fromList [("key1", HV "abc"), ("key2", HV 123)] :: MyMap
let myData = MyData myMap
getMyMap myData 
Run Code Online (Sandbox Code Playgroud)

And*_*ewC 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
Run Code Online (Sandbox Code Playgroud)

现在,一旦您封装了数据,就可以使用它做任何您喜欢的事情,就像它是普通的数据类型一样,并恢复任何Contracted功能,如下所示:

munged :: Encapsulated -> String
munged (Encapsulate a) = toString.alter.alter.alter $ a
Run Code Online (Sandbox Code Playgroud)

如果你喜欢的话,你可以存储一大堆的Encapsulated数据地图,没有必要做任何特殊或重新实现Data.Map你的存在,因为,这里的强大功能模式:Data.Map没有对任何数据的假设.它是参数多态,适用于任何事物.任何东西,甚至是功能.它所做的唯一假设是你的密钥是可排序的(Ord k =>)而你的数据是同质的(我们的GADT是同质的,尽管是由异构数据构成的).


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


Dan*_*ton 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
Run Code Online (Sandbox Code Playgroud)

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

然后,您可以将数据强制转换为Dynamicwith 类型,并从with 类型toDyn安全地强制转换。DynamicfromDynamic


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

data Cat = ...
data Witch = ...
data Ghoul = ...
Run Code Online (Sandbox Code Playgroud)

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

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 ...
Run Code Online (Sandbox Code Playgroud)

  • 每种方法都有优点和缺点;虽然有些人(如 Luke Palmer)认为最好避免使用类型类,除非在某些情况下,但其他人(如 Edward Kmett)则拥抱面向对象编程的类型类。(这些观点不一定是矛盾的,但关于什么是“惯用”和什么是“反模式”,肯定存在很多灰色地带。)如果你想在 Haskell 中做类似 OO 的事情,那么你应该查看“lenses” ”。 (2认同)