2014-11-04 2 views
3

하스켈을 배우면서, 나는 C++ 헤더 파일의 포맷터를 작성한다. 먼저, 모든 클래스 멤버를 -a-collection-of-class-members으로 구문 분석 한 다음 서식 지정 루틴으로 전달합니다. 내가 하스켈의 데이터 유형 디자인

data ClassMember = CmTypedef Typedef | 
        CmMethod Method | 
        CmOperatorOverload OperatorOverload | 
        CmVariable Variable | 
        CmFriendClass FriendClass | 
        CmDestructor Destructor 

이 클래스 멤버를 표현하기 위해 (때문에 나는 포맷 스타일의 일부 특수성의 클래스 회원이 방법을 분류 할 필요가있다.)

날 귀찮게 문제입니다 어떤 기능을 "드래그"에 클래스 멤버 유형에 대해 ClassMember 레벨로 정의하면 중복 된 코드를 많이 작성해야합니다. 예를 들어,

한편
instance Formattable ClassMember where 
    format (CmTypedef td) = format td 
    format (CmMethod m) = format m 
    format (CmOperatorOverload oo) = format oo 
    format (CmVariable v) = format v 
    format (CmFriendClass fc) = format fc 
    format (CmDestructor d) = format d 

instance Prettifyable ClassMember where 
    -- same story here 

, 나는 확실히 때문에하지 않는 것

data ClassMember a = ClassMember a 

instance Formattable ClassMember a 
    format (ClassMember a) = format a 

로 정의 (적어도 난 그렇게 생각) ClassMember 개체의 목록을 가지고 싶습니다 옵션이 될 수 있습니다. 인스턴스 자체를 반대하지 ClassMember

  1. 스토어,하지만 기능은 형식 루틴에 필요한 해당 유형에 정의 : 내가 고려하고

    대안입니다. 이 방법은 구문 분석 결과에 따라 [ClassMember]으로 표시되는 모듈성을 손상시킵니다. IMO는 모든 용도를 알고 있어야합니다.

  2. ClassMember을 실재 유형으로 정의하십시오. 따라서 [ClassMember]은 더 이상 문제가되지 않습니다. 이 디자인이 충분히 엄격한 지 의심스럽고 다시 정의하면 정의에 모든 제약 조건을 지정해야합니다 (예 : data ClassMember = forall a . Formattable a => ClassMember a). 또한 확장을 사용하지 않고 솔루션을 선호합니다.

내가 하스켈에서 적절한 방법을 사용하고 있습니까? 아니면 더 좋은 방법이 있습니까?

+1

왜 먼저 Formattable 클래스와 Prettifyable 클래스가 필요합니까? 'ClassMember'가 아닌 무언가를'포맷팅 '할 것인가? –

+0

@BenjaminHodgson : 예, 이것이 문제입니다. 실제 포맷은'data FormattableItem = FiClassMember ClassMember (아마 SingleLineComment)입니다. FiSingleLineComment SingleLineComment | FiComment Comment | FiScopeModifier AccessModifier'를 호출합니다. 형식 지정 규칙은 약간 멋지다 :'ClassMember' 오브젝트는 다른 'FormattableItem' 오브젝트에 의해 그룹으로 분할되고 그룹 내에서 또한 서로 의존합니다. – AdelNick

답변

4

먼저 ADT를 약간 줄이십시오. 연산자 오버로드 및 소멸자는 특별한 종류의 메서드이므로 CmMethod에서 세 가지 모두를 처리하는 것이 더 적합 할 수 있습니다. Method은 그런 다음이를 구분하는 특별한 방법을 갖습니다. 또는 세 가지를 모두 CmMethod, CmOperatorOverloadCmDestructor으로 유지하고 모두 동일한 Method 유형을 포함하도록하십시오.

물론 복잡성을 너무 줄일 수 있습니다.

예 : Show 인스턴스의 구체적인 예는 일부 특수한 경우를 제외하고는 직접 작성하지 않는 것입니다. 귀하의 경우를 들어, 인스턴스가 자동으로 파생 된이 훨씬 더 합리적하십시오 포함 된 결과를 보여주는 것은 또한 생성자에 대한 정보를 제공한다 : 이것은 당신이 잘못 때문에 사용자 정의 예를 –에서 다른 결과를 줄 것이다

data ClassMember = CmTypedef Typedef 
       | CmMethod Method 
       | ... 
       | CmDestructor Destructor 
       deriving (Show) 

.

당신이 정말로 Show에 관심이 있지만 ClassMember의 – 아니라 더 구체적인 무언가를 다른 클래스 C에 대해 얘기하지 않은 경우에, 당신은 아마 처음부터 C를 정의해서는 안됩니다! 유형 클래스의 목적은 매우 다양한 유형에 적용되는 수학 개념을 표현하는 것입니다.

+0

의견을 보내 주셔서 감사합니다. 실제로 '쇼'가 혼란 스럽다. 나는 그것을 'Formattable'으로 변경했다. 또한 내가 왜 모든 생성자를 가지고 있는지 명확하게 설명했다. – AdelNick

0

가능한 해결 방법은 레코드를 사용하는 것입니다. 확장자없이 사용할 수 있으며 유연성이 유지됩니다.

일부 상용구 코드가 있지만 한 번만 입력해야합니다. 따라서 ClassMember에 대해 다른 작업 집합을 수행해야하는 경우 매우 쉽고 빠르게 수행 할 수 있습니다.

{-# LANGUAGE TemplateHaskell #-} 

module Test.ClassMember 

import Control.Lens 

-- | The class member as initially defined. 
data ClassMember = 
     CmTypedef Typedef 
    | CmMethod Method 
    | CmOperatorOverload OperatorOverload 
    | CmVariable Variable 
    | CmFriendClass FriendClass 
    | CmDestructor Destructor 

-- | Some dummy definitions of the data types, so the code will compile. 
data Typedef = Typedef 
data Method = Method 
data OperatorOverload = OperatorOverload 
data Variable = Variable 
data FriendClass = FriendClass 
data Destructor = Destructor 

{-| 
A data type which defines one function per constructor. 
Note the type a, which means that for a given Hanlder "a" all functions 
must return "a" (as for a type class!). 
-} 
data Handler a = Handler 
    { 
     _handleType  :: Typedef -> a 
    , _handleMethod  :: Method -> a 
    , _handleOperator :: OperatorOverload -> a 
    , _handleVariable :: Variable -> a 
    , _handleFriendClass :: FriendClass -> a 
    , _handleDestructor :: Destructor -> a 
    } 

{-| 
Here I am using lenses. This is not mandatory at all, but makes life easier. 
This is also the reason of the TemplateHaskell language pragma above. 
-} 
makeLenses ''Handler 

{-| 
A function acting as a dispatcher (the boilerplate code!!!), telling which 
function of the handler must be used for a given constructor. 
-} 
handle :: Handler a -> ClassMember -> a 
handle handler member = 
    case member of 
     CmTypedef a   -> handler^.handleType $ a 
     CmMethod a   -> handler^.handleMethod $ a 
     CmOperatorOverload a -> handler^.handleOperator $ a 
     CmVariable a   -> handler^.handleVariable $ a 
     CmFriendClass a  -> handler^.handleFriendClass $ a 
     CmDestructor a)  -> handler^.handleDestructor $ a 

{-| 
A dummy format method. 
I kept things simple here, but you could define much more complicated 
functions. 

You could even define some generic functions separately and... you could define 
them with some extra arguments that you would only provide when building 
the Handler! An (dummy!) example is the way the destructor function is 
constructed. 
-} 
format :: Handler String 
format = Handler 
    (\x -> "type") 
    (\x -> "method") 
    (\x -> "operator") 
    (\x -> "variable") 
    (\x -> "Friend") 
    (destructorFunc $ (++) "format ") 

{-| 
A dummy function showcasing partial application. 
It has one more argument than handleDestructor. In practice you are free 
to add as many as you wish as long as it ends with the expected type 
(Destructor -> String). 
-} 
destructorFunc :: (String -> String) -> Destructor -> String 
destructorFunc f _ = f "destructor" 

{-| 
Construction of the pretty handler which illustrates the reason why 
using lens by keeping a nice and concise syntax. 

The "&" is the backward operator and ".~" is the set operator. 
All we do here is to change the functions of the handleType and the 
handleDestructor. 
-} 
pretty :: Handler String 
pretty = format & handleType  .~ (\x -> "Pretty type") 
       & handleDestructor .~ (destructorFunc ((++) "Pretty ")) 

을 그리고 지금 우리는 몇 가지 테스트를 실행할 수 있습니다 : 여기

이 특정 사건에 대한 예입니다 (템플릿은 하스켈과 Control.Lens은 일을 더 쉽게되지만 필수는 아니다한다)

test1 = handle format (CmDestructor Destructor) 
> "format destructor" 

test2 = handle pretty (CmDestructor Destructor) 
> "Pretty destructor" 
관련 문제