Haskell 如何定义严格存在类型?

Haskell 如何定义严格存在类型?,haskell,existential-type,Haskell,Existential Type,我想在严格的上下文中使用Haskell的存在类型()。我从haskell wiki中获取了这个示例,并试图用它创建一个严格的异构映射。需要对地图及其值进行全面评估 我定义了3种类型来测试这一点。第一个只是一个简单的严格映射。第二种类型是使用存在类型的异构映射。第三种类型与第二种类似,但添加了NFData约束 虽然第一个简单的例子是真正严格的,并且得到了充分的评估,但其他的例子则不是。即使是使用deepseq的第三种类型,似乎也没有得到充分的评估 我的问题是: 如何严格定义这种异构类型 如果不可

我想在严格的上下文中使用Haskell的存在类型()。我从haskell wiki中获取了这个示例,并试图用它创建一个严格的异构映射。需要对地图及其值进行全面评估

我定义了3种类型来测试这一点。第一个只是一个简单的严格映射。第二种类型是使用存在类型的异构映射。第三种类型与第二种类似,但添加了NFData约束

虽然第一个简单的例子是真正严格的,并且得到了充分的评估,但其他的例子则不是。即使是使用deepseq的第三种类型,似乎也没有得到充分的评估

我的问题是:

  • 如何严格定义这种异构类型
  • 如果不可能,为什么不呢?有办法解决这个问题吗
示例源

{-# LANGUAGE ExistentialQuantification #-}

import GHC.AssertNF
import Control.DeepSeq
import Data.Map.Strict

-- 1) simple container

data Obj a = Obj a

-- using a smart constructor here to ensure arbitrary values are strict
mkObj :: a -> Obj a
mkObj a = Obj $! a

-- using a special String constructor to ensure Strings are always 
-- fully evaluated in this example
mkString :: String -> String
mkString x = force x

xs :: Map Int (Obj String)
xs = fromList [ (1, mkObj . mkString $ "abc")
              , (2, mkObj . mkString $ "def")
              , (3, mkObj . mkString $ "hij")
              ]

-- 2) container using existential quantification

data Obj2 = forall a. (Show a) => Obj2 a

-- using the smart constructor here has no effect on strictness
mkObj2 :: Show a => a -> Obj2
mkObj2 a = Obj2 $! a

xs2 :: Map Int Obj2
xs2 = fromList [ (1, mkObj2 1)
               , (2, mkObj2 . mkString $ "test")
               , (3, mkObj2 'c')
               ]

-- 3) container using existential quantification and deepseq

data Obj3 = forall a. (NFData a, Show a) => Obj3 !a

instance NFData Obj3 where
  -- use default implementation

mkObj3 :: (NFData a, Show a) => a -> Obj3
mkObj3 a = Obj3 $!! a

xs3 :: Map Int Obj3
xs3 = fromList [ (1, mkObj3 (1::Int))
               , (2, mkObj3 . mkString $ "abc")
               , (3, mkObj3 ('c'::Char))
               ]

-- strictness tests
main :: IO ()
main = do
  putStr "test: simple container: "
  (isNF $! xs) >>= putStrLn . show
  assertNF $! xs
  putStr "test: heterogeneous container: "
  (isNF $! xs2) >>= putStrLn . show
  assertNF $! xs2
  putStr "test: heterogeneous container with NFData: " 
  (isNF $!! xs3) >>= putStrLn . show
  assertNF $!! xs3 
  return ()
GHCI输出

test: simple container: True
test: heterogeneous container: False
Parameter not in normal form: 1 thunks found:
let x1 = Tip()
in Bin (I# 2) (Obj2 (_sel (_bh (...,...))) (C# 't' : C# 'e' : ... : ...)) (Bin (I# 1) (Obj2 (D:Show _fun _fun _fun) (S# 1)) x1 x1 1) (Bin (I# 3) (Obj2 (D:Show _fun _fun _fun) (C# 'c')) x1 x1 1) 3
test: heterogeneous container with NFData: False
Parameter not in normal form: 1 thunks found:
let x1 = _ind ...
    x2 = Tip()
in _bh (Bin (I# 2) (Obj3 (_bh (_fun x1)) (_sel (_bh (...,...))) (C# 'a' : C# 'b' : ... : ...)) (Bin (I# 1) (Obj3 (_ind _fun) (D:Show _fun _fun _fun) (I# 1)) x2 x2 1) (Bin (I# 3) (Obj3 x1 (D:Show _fun _fun _fun) (C# 'c')) x2 x2 1) 3)

请注意,以下数据类型

data Obj2 = forall a. (Show a) => Obj2 a
data Obj3 = forall a. (NFData a, Show a) => Obj3 !a
实际上存储类字典和数据,例如,
Obj2
实际上有两个字段。您的智能构造函数强制数据字段严格, 但是你不能直接控制字典。我怀疑强迫还是不强迫 这本词典在实践中会有很大的不同,但你也许能欺骗对方 我不想这样做。例如,以下变量似乎适用于
Obj2

mkObj2 :: Show a => a -> Obj2
mkObj2 a = showsPrec 0 a `seq` (Obj2 $! a)
您还可以看到,以下两项将“起作用”:


信不信由你,你的三项测试都很严格!从某种意义上说,您存储的“异构对象”在放入容器对象之前会进行评估

不严格的只是存在主义的实现。问题是,Haskell实际上没有存在主义,它们是由存储类型类dictonary的记录类型模拟的。在您仅使用
Show
约束的情况下,这基本上意味着您不存储对象,而只存储其
Show
的结果,这是一个字符串。但GHC无法知道您希望严格评估该字符串;事实上,这通常不是一个好主意,因为
show
通常比深入评估对象要昂贵得多。因此,
show
将在调用它时进行评估,这在我看来很好

如果您确实希望严格评估
show
,唯一可以确保的方法是明确记录转换。在您的示例中,这很简单:

newtype Obj2 = Obj2 { showObj2 :: String }
mkObj2 :: Show a => a -> Obj2
mkObj2 = (Obj2 $!) . show

你也许能够更简洁地解决问题,并通过以下方式更好地控制严格程度。在没有上下文的情况下,您确实感觉有点像是在试图重新创建面向对象的范例,而来到Haskell完全不同的功能世界只是为了做OO,这将是一种耻辱。(为什么要去法国买麦当劳?)
newtype Obj2 = Obj2 { showObj2 :: String }
mkObj2 :: Show a => a -> Obj2
mkObj2 = (Obj2 $!) . show