I want to use Haskell's existential types (http://www.haskell.org/haskellwiki/Existential_type) in a strict context. I took the example from the haskell-wiki and tried to create a strict heterogeneous map with it. It's required that the map and it's values get fully evaluated.
I defined 3 types to test this. The first one is just a simple strict map. The second type is a heterogeneous map using existential types. The third type is like the second, but adds NFData constraints.
While the first simple example is truly strict and gets fully evaluated, the other ones are not. Even the third type, using deepseq, seems not to be fully evaluated.
My questions are:
- How do i define such a heterogeneous type in a strict way?
- If it's not possible - Why not? Is there a way to work around this?
Example source
{-# 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 output
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)