2013-04-19 12 views
3

은 가정하자 나는 다음과 같은 하스켈 코드를 한 : 위의 코드에서하스켈 타입

data Option 
    = Help 
    | Opt1 Int Double String 
    -- more options would be here in a real case 

handleOption :: Option -> IO() 
handleOption option = case option of 
    Help -> handleHelp 
    Opt1 n f s -> handleOpt1 n f s 

handleHelp :: IO() 
handleHelp = print "help" 

handleOpt1 :: Int -> Double -> String -> IO() 
handleOpt1 n f s = print (n, f, s) 

, 내가를 유지할 수 있다는 점에서 사전의 목적을 해체 나에게 낭비를 보인다 데이터 묶음. 이제 Opt1의 각 부분을 개별적으로 전달하거나 함께 전달하기 위해 하나의 개별 데이터 형식을 만들어야합니다. handleOpt1 Help을 컴파일 오류로 만드는 등 Option 인스턴스를 전달할 수 없도록하는 동안 전체 Opt1handleOpt1으로 전달할 수 있습니까? 아래

예 의사 코드 :


data Option 
    = Help 
    | Opt1 Int Double String 

handleOption :: Option -> IO() 
handleOption option = case option of 
    Help -> handleHelp 
    opt1 @ Opt1{} -> handleOpt1 opt1 

handleHelp :: IO() 
handleHelp = print "help" 

handleOpt1 :: Option:Opt1 -> IO() 
handleOpt1 (Opt1 n f s) = print (n, f, s) 
+0

handleOpt1 (Opt1Params N F의 S) = .... 'handleOpt1'에 전달 된 값이 오른쪽 생성자로 생성되었는지를 검사 할 수 없습니다 (일반적으로 소스 코드에서 명시 적으로 전달 된 값을 확인할 수 있지만 일반적으로 전달 된 값은 런타임 중에 계산되므로 아무도 예외적 인 경우를 검사하기위한 코드를 작성했습니다). –

+0

당신은 옵션에 대한 typeclass를 만들 수 있습니다. 실존 적 수량화를 사용하여 그들을 결합하고 (그 조합으로부터 유용한 정보를 추출하는) 몇 가지 방법을 찾아 낼 수 있습니다 ... 아니면 사소한 것들에 대해 걱정할 필요가 없습니다. 프로그램에 성능을 저하시키는 무언가가 있다면, 아마도 그렇지 않을 것입니다. – Cubic

+0

"낭비"란 말은 성능 수준이 아니라 디자인 수준을 의미했습니다. 내 게시물을 업데이트했습니다. –

답변

6

이 경우 GADTs을 사용할 수 있습니다.

{-# LANGUAGE GADTs #-} 

data Option a where 
    Help :: Option() 
    Opt1 :: Int -> Double -> String -> Option (Int, Double, String) 

handleOption :: Option a -> IO() 
handleOption option = case option of 
    Help   -> handleHelp 
    opt1 @ Opt1{} -> handleOpt1 opt1 

handleHelp :: IO() 
handleHelp = print "help" 

handleOpt1 :: Option (Int, Double, String) -> IO() 
handleOpt1 (Opt1 n f s) = print (n, f, s) 

GADT를 사용하면 더 많은 유형 정보를 컴파일러에 제공 할 수 있습니다. 그것은 단지 Option (Int, Double, String)을 허용하기 때문에 handleOpt1를 들어, 컴파일러는 Option() (즉 Help)가 전달되지 않습니다 알고있다. 말했다

는 GADTs을 사용하여 열심히 꽤 몇 가지 다른 일을한다. 예를 들어 자동 유도 (예 : deriving (Eq, Show))는 일반적으로 자동 검색에서 작동하지 않습니다. 당신은 신중하게 당신의 경우에 그들을 사용하는 장단점을 고려해야합니다.

handleOption :: Option -> IO() 

handleOption Help = print "help" 

handleOption (Opt1 n f s) = print (n, f, s) 

이 당신에게 두 세계의 최고를 얻을이 특정 예에서

+0

유형 부서에서 엄청난 잔인한 것처럼 보입니다. –

2

GHC의 인라인 handleHelphandleOpt1, 따라서 호출 오버 헤드를 피할 수있는 큰 기회가있다 - 확실히 알아내는 look at the generated Core (컴파일러의 중간 표현). 어떤 이유로,이 함수는 인라인되지 않는, 경우

, 당신은 mark them with the INLINE pragma 할 수 있습니다

handleOpt1 :: Option -> IO() 
handleOpt1 (Opt1 n f s) = print (n, f, s) 
handleOpt1 _ = undefined 
:

handleHelp :: IO() 
handleHelp = print "help" 
{-# INLINE handleHelp #-} 

handleOpt1 :: Option -> IO() 
handleOpt1 (Opt1 n f s) = print (n, f, s) 
{-# INLINE handleOpt1 #-} 

또한 handleOption의 인수를 해체 방지하기 위해 inliner에 의존 할 수

undefined은 비 포괄적 패턴 일치에 대한 경고를 그냥 울리는 것입니다. 또는이 줄을 제거하고이 모듈에 -fno-warn-incomplete-patterns을 사용할 수 있습니다.

우리가 handleOpt1undefined 지점이 제거 된 것을 볼 수 있습니다 생성 된 코어를 보면 : 그것은 handleOpt1에서 패턴 일치 실패의 가능성을 배제하기 때문에
handleOpt2 
    :: Option 
    -> State# RealWorld 
    -> (# State# RealWorld,() #) 
handleOpt2 = 
    \ (ds_dl7 :: Option) 
    (eta_Xh :: State# RealWorld) -> 
    case ds_dl7 of _ { 
     Help -> ... 
     Opt1 n_aaq f_aar s_aas -> ... 

main1 
    :: State# RealWorld 
    -> (# State# RealWorld,() #) 
main1 = 
    \ (eta_Xk :: State# RealWorld) -> 
    handleOpt2 (Opt1 2 3.0 "") eta_Xk 

내가하지만, 원래 버전을 선호합니다.

+0

여기 성능에 대해 걱정하지 않습니다. 나는 유형 안전과 데이터 묶음 보관에 관심이 있습니다. –

+0

@ThomasEding 답변을 업데이트했습니다. –

2

handleHelphandleOpt1 죽겠다하고 그들에게 handleOption 기능 모두 별도의 방정식을함으로써 "문제"를 해결하는 방법이 더 자연스러운 것 같다 .각각의 경우에 대해 별도의 방정식을 작성할 수 있습니다 (따라서 각 사례가 커서 하나의 거대한 방정식에 녹아 들지는 않더라도) 상용구 "파견"기능을 쓸 필요가 없으며 케이스를 실제로 사용해야 할 때까지 Opt1 케이스의 부품 이름을 지정하십시오.

0

저는 Ben의 답변을 좋아하지만, 더 많은 유형을 소개 할 수 있습니다.

데이터 Opt1Params = Opt1Params 지능을 두 번 문자열

데이터 옵션 = 도움말 | OPT1 Opt1Params

handleOption 도움말 = handleHelp handleOption은 (OPT1 PARAMS) = handleOpt1는 PARAMS 컴파일러 유형만이 올바른지 확인할 수