F #

2

에서 작은 AST에 대해 구분 된 공용체 또는 레코드 유형을 사용할지 여부 선택 아주 간단한 장난감 언어 구문 분석기를 구현한다고 가정 해 보겠습니다. DU 또는 레코드 유형을 사용할지 여부를 결정합니다 (둘 다 혼합 할 수 있습니까?). 언어의 구조는 다음과 같습니다이 간단한 언어로 프로그램의F #

a Namespace consists of a name and a list of classes 
a Class consists of a name and a list of methods 
Method consists of a name, return type and a list of Arguments 
Argument consists of a type and a name 

예 :

namespace ns { 
    class cls1 { 
    void m1() {} 
    } 

    class cls2 { 
    void m2(int i, string j) {} 
    } 
} 

어떻게이 모델 왜 것?

답변

6

대체로 DU를 사용하여 코드 구조의 일부가 여러 가지 가능성 중 하나 일 수있는 대안을 구현하려고합니다. 레코드 대신에 튜플을 사용할 수 있지만, 튜플에 이름이 지정된 항목이 없기 때문에 사용하기가 더 쉽지만 읽기 및 유지 관리가 더 어려울 수있는 혼합 형식을 사용하는 것이 이상적입니다.

나는 하위 사용자에 대한 필요성이 예를 들어 언어를 사용하여 분명하지 않을 수도 있습니다이

type CompilationUnit = | Namespace list 

and Namespace = { Name : String 
        Body : NamespaceBody } 

and NamespaceBody = | Classes of Class list 

and Class = { Name : String 
       Body : ClassBody } 

and ClassBody = | Members of Member list 

and Member = | Method of Method 

and Method = { Name : String 
       Parameters : Parameter list option 
       ReturnType : TypeName option 
       Body : MethodBody } 

and Parameter = { Name : String 
        Type : TypeName } 

and MethodBody = ... 

and TypeName = ... 

같은로 모델링 것입니다,하지만 당신은 하나가 될 수있는 코드의 어떤 지점이 명확 빨리 될 것이다 더 많은 항목. 예를 들어 클래스에 필드를 추가하는 경우 Member에 새로운 Field 차별을 추가하기 만하면됩니다.

문법을 사용하여 언어를 구문 분석하는 경우 (LL/LALR 등) 문법에있는 각 대체 규칙마다 일치하는 DU가 필요할 수 있습니다.

+0

흠을 모두 너무 혼합, 나는 그것을 기대하고 있었다. –

+2

나는 단지 튜플로 DU를 사용하는 경향이있다. 작성하기가 쉽지만 인수가 두 가지 이상인 곳에서는 혼란 스러울 수 있습니다. 설명서의 내용에 따라 각각 무엇인지 알려줍니다. 예를 들어, Method의 Parameters의 경우,'Parameters : (String * TypeName) list option' 옵션을 대신 사용할 수 있습니다. 튜플의 각 항목이 나타내는 것은 분명합니다. 그러나 TypeName이 아닌 Type에도 String을 사용하고'(String * String)'을 사용했다면 각각의 목적을 이해할 수있는 레코드가 필요합니다. –

+0

나는 가독성 측면에서 일반적으로 오류를 일으키므로 투플 대신 레코드를 사용한다. –

0

네임 스페이스는 이름과 클래스 클래스는 이름으로 구성 목록과 방법은 이름, 반환 형식 및 인수의 목록으로 구성 방법의 목록으로 구성이 인수 유형으로 구성되어 있으며 이 간단한 언어로 프로그램의 이름 예 :

당신은 또한 당신의 타입 시스템에 대한 유형 정의가 필요하고 실제로 조합 유형이 가치가있는 유일한 장소입니다

:

type Type = Void | Int | String 

언어의 유형은 int 또는 문자열 또는 void이지만 아무 것도 사용할 수 없습니다 (예 : null), 그러한 옵션은 복수 일 수 없습니다.

네임 스페이스의 유형은 다음과 같이 완전히 익명 수 :

string * (string * (Type * string * (Type * string) list) list) list 

는이처럼 예 네임 스페이스를 정의 할 수 있습니다 :

실제로
"ns", ["cls1", [Void, "m1", []] 
     "cls2", [Void, "m2", [Int, "i"; String, "j"]]] 

, 당신은 아마 넣을 수있는 기능을 원하는 다른 네임 스페이스에 네임 스페이스를 추가하고 클래스를 클래스에 추가하여 코드를 다음과 같이 변형 할 수 있습니다.

type Type = 
    | Void 
    | Int 
    | String 
    | Class of Map<string, Type> * Map<string, Type * (Type * string) list> 

type Namespace = 
    | Namespace of string * Namespace list * Map<string, Type> 

Namespace("ns", [], 
      Map 
      [ "cls1", Class(Map[], Map["m1", (Void, [])]) 
       "cls2", Class(Map[], Map["m2", (Void, [Int, "i"; String, "j"])])]) 

익명 유형은 혼란의 원인이되지 않는 한 괜찮습니다. 엄지 손가락의 규칙에 따라 두 개 또는 세 개의 필드가 있고 서로 다른 유형 (예 : "메서드") 인 경우 튜플이 좋습니다. 동일한 유형의 필드가 더 많거나 여러 필드가있는 경우 레코드 유형으로 전환해야합니다.

그래서이 경우 당신은 방법에 대한 레코드 유형을 소개 할 수 있습니다 :

type Method = 
    { ReturnType: Type 
    Arguments: (Type * string) list } 

and Type = 
    | Void 
    | Int 
    | String 
    | Class of Map<string, Type> * Map<string, Method> 

type Namespace = 
    | Namespace of string * Namespace list * Map<string, Type> 

Namespace("ns", [], 
      Map 
      [ "cls1", Class(Map[], Map["m1", { ReturnType = Void; Arguments = [] }]) 
       "cls2", Class(Map[], Map["m2", { ReturnType = Void; Arguments = [Int, "i"; String, "j"] }])]) 

어쩌면 도우미 함수를 레코드 구성하기 :

let Method retTy name args = 
    name, { ReturnType = retTy; Arguments = args } 

Namespace("ns", [], 
      Map 
      [ "cls1", Class(Map[], Map[Method Void "m1" []]) 
       "cls2", Class(Map[], Map[Method Void "m2" [Int, "i"; String, "j"]])])