2014-01-14 2 views
4

엄격한 컨텍스트에서 하스켈의 존재 유형 (http://www.haskell.org/haskellwiki/Existential_type)을 사용하고 싶습니다. 나는 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) 
+0

당신은 (HTTP [당신이 실제로 그것에서 다시 무엇을 필요로 실존 형 클래스를 대체 의해 더 간결하게 문제를 해결하고 엄격 더 잘 제어를 할 수 있습니다 ://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/). 문맥이없는 상황에서 여러분은 객체 지향 패러다임을 재현하려고하는 것처럼 느껴질 것입니다. 그리고 OO를하기 위해서만 하스켈의 다소 다른 기능적 세계에 오는 것은 수치 스러울 것입니다. (일명 MacDonalds를 사기 위해 프랑스에가는 이유는 무엇입니까?) –

답변

9

이 믿거 나 말거나지만, 세 가지 테스트 너의 것의 엄격하다! 컨테이너 객체에 넣기 전에 저장하는 "heterogeneos 객체"가 평가됩니다.

엄격하지 않은 것은 실존의 구현입니다. 사실, 하스켈은 실제로 존재하지 않습니다, 그들은 형식 ​​클래스 dictonaries를 저장하는 레코드 유형에 의해 에뮬레이트됩니다. 단지 Show 제약의 경우에는 기본적으로 객체를 저장하지 않고 문자열 인 show의 결과 만 저장한다는 의미입니다. 그러나 GHC는 문자열로 엄격히 평가해야한다는 것을 알 수 없습니다. 사실 show은 일반적으로 객체를 심층 평가하는 것보다 훨씬 비쌉니다. 따라서 show은 호출 할 때 평가되는 상태로 남아 있습니다. 이는 매우 정밀한 IMO입니다.

show을 정확하게 평가하려면 레코드 변환을 명시 적으로 만드는 것이 유일한 방법입니다. 당신의 예에서, 그 사소한 :

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

참고 예

실제로
data Obj2 = forall a. (Show a) => Obj2 a 
data Obj3 = forall a. (NFData a, Show a) => Obj3 !a 

같은 데이터 형식 데이터뿐만 아니라 클래스 사전을 저장하십시오. 예를 들어, Obj2 에는 실제로 두 개의 필드가 있습니다. 스마트 생성자는 데이터 필드가 엄격하게 적용되도록합니다 ( ). 사전에는 직접 제어권이 없습니다. 나는 강제로 강제로 또는 강제로 사전은 연습에 많은 차이를 만들지 만, 당신은 컴파일러를 그렇게 속일 수 있습니다. 당신은 또한 다음과 같은 두 가지가 "작업"것을 볼 수 있습니다

mkObj2 :: Show a => a -> Obj2 
mkObj2 a = showsPrec 0 a `seq` (Obj2 $! a) 

: 예를 들어, 다음과 같은 변형 Obj2 나를 위해 작동하는 것 같다

data Obj2a = forall a. Obj2a a 

mkObj2a a = Obj2a $! a 

xs2a :: Map Int Obj2a 
xs2a = fromList [ (1, mkObj2a 1) 
       , (2, mkObj2a . mkString $ "test") 
       , (3, mkObj2a 'c') 
       ] 

data Obj2b = forall a. Obj2b (a -> String) a 

mkObj2b :: Show a => a -> Obj2b 
mkObj2b a = (Obj2b $! show) $! a 

xs2b :: Map Int Obj2b 
xs2b = fromList [ (1, mkObj2b 1) 
       , (2, mkObj2b . mkString $ "test") 
       , (3, mkObj2b 'c') 
       ] 
관련 문제