2013-08-04 44 views
5

tôi đã xác định một cấu trúc cây biểu hiện trong F # như sau:trận đấu Incomplete với VÀ mẫu

type Num = int 
type Name = string 
type Expr = 
    | Con of Num 
    | Var of Name 
    | Add of Expr * Expr 
    | Sub of Expr * Expr 
    | Mult of Expr * Expr 
    | Div of Expr * Expr 
    | Pow of Expr * Expr 
    | Neg of Expr 

tôi muốn để có thể khá-in cây biểu hiện vì vậy tôi đã làm như sau:

let (|Unary|Binary|Terminal|) expr = 
    match expr with 
    | Add(x, y) -> Binary(x, y) 
    | Sub(x, y) -> Binary(x, y) 
    | Mult(x, y) -> Binary(x, y) 
    | Div(x, y) -> Binary(x, y) 
    | Pow(x, y) -> Binary(x, y) 
    | Neg(x) -> Unary(x) 
    | Con(x) -> Terminal(box x) 
    | Var(x) -> Terminal(box x) 

let operator expr = 
    match expr with 
    | Add(_) -> "+" 
    | Sub(_) | Neg(_) -> "-" 
    | Mult(_) -> "*" 
    | Div(_) -> "/" 
    | Pow(_) -> "**" 
    | _ -> failwith "There is no operator for the given expression." 

let rec format expr = 
    match expr with 
    | Unary(x) -> sprintf "%s(%s)" (operator expr) (format x) 
    | Binary(x, y) -> sprintf "(%s %s %s)" (format x) (operator expr) (format y) 
    | Terminal(x) -> string x 

Tuy nhiên, tôi không thực sự thích cách tiếp cận failwith cho hàm operator vì nó không an toàn trong thời gian biên dịch. Vì vậy, tôi viết lại nó như là một mô hình hoạt động:

let (|Operator|_|) expr = 
    match expr with 
    | Add(_) -> Some "+" 
    | Sub(_) | Neg(_) -> Some "-" 
    | Mult(_) -> Some "*" 
    | Div(_) -> Some "/" 
    | Pow(_) -> Some "**" 
    | _ -> None 

Bây giờ tôi có thể viết lại chức năng format của tôi đẹp như sau:

let rec format expr = 
    match expr with 
    | Unary(x) & Operator(op) -> sprintf "%s(%s)" op (format x) 
    | Binary(x, y) & Operator(op) -> sprintf "(%s %s %s)" (format x) op (format y) 
    | Terminal(x) -> string x 

tôi cho rằng, kể từ khi F # là ma thuật, mà điều này chỉ có thể làm việc. Thật không may, trình biên dịch cảnh báo tôi về các kết quả mẫu không hoàn chỉnh, vì không thể thấy bất kỳ thứ gì khớp với Unary(x) cũng sẽ khớp với Operator(op) và bất kỳ thứ gì khớp với Binary(x, y) cũng sẽ khớp với Operator(op). Và tôi xem xét các cảnh báo như vậy là xấu như lỗi trình biên dịch. Vì vậy, câu hỏi của tôi là: Có một lý do cụ thể tại sao điều này không hoạt động (như tôi đã để lại một số chú giải phép thuật ở đâu đó hoặc có điều gì đó mà tôi không thấy)? Có cách giải quyết đơn giản nào tôi có thể sử dụng để có được loại an toàn tôi muốn không? Và có một vấn đề cố hữu với kiểu kiểm tra biên dịch thời gian này hay là một thứ gì đó mà F # có thể thêm vào trong một số bản phát hành trong tương lai?

+2

Tôi nghĩ rằng loại vấn đề này không thể sửa được. Trong trường hợp chung, nó sẽ yêu cầu giải quyết vấn đề dừng. Tôi nghĩ rằng giải pháp thanh lịch nhất sẽ là thêm một lớp thêm các mẫu để bạn trả về 'Unary (x, op)'. –

+0

Tôi thực sự xem xét việc đó, nhưng tôi muốn giữ mô hình của mình cụ thể cho một trường hợp sử dụng (phân loại tính xác thực của biểu thức và trích xuất các đối số của nó). – luksan

Trả lời

3

Nếu bạn mã sự tuyệt chủng giữa các thuật ngữ nền tảng và cụm từ phức tạp trong hệ thống kiểu, bạn có thể tránh kiểm tra thời gian chạy và biến chúng trở thành mẫu khớp hoàn chỉnh.

type Num = int 
type Name = string 

type GroundTerm = 
    | Con of Num 
    | Var of Name 
type ComplexTerm = 
    | Add of Term * Term 
    | Sub of Term * Term 
    | Mult of Term * Term 
    | Div of Term * Term 
    | Pow of Term * Term 
    | Neg of Term 
and Term = 
    | GroundTerm of GroundTerm 
    | ComplexTerm of ComplexTerm 


let (|Operator|) ct = 
    match ct with 
    | Add(_) -> "+" 
    | Sub(_) | Neg(_) -> "-" 
    | Mult(_) -> "*" 
    | Div(_) -> "/" 
    | Pow(_) -> "**" 

let (|Unary|Binary|) ct = 
    match ct with 
    | Add(x, y) -> Binary(x, y) 
    | Sub(x, y) -> Binary(x, y) 
    | Mult(x, y) -> Binary(x, y) 
    | Div(x, y) -> Binary(x, y) 
    | Pow(x, y) -> Binary(x, y) 
    | Neg(x) -> Unary(x) 

let (|Terminal|) gt = 
    match gt with 
    | Con x -> Terminal(string x) 
    | Var x -> Terminal(string x) 

let rec format expr = 
    match expr with 
    | ComplexTerm ct -> 
     match ct with 
     | Unary(x) & Operator(op) -> sprintf "%s(%s)" op (format x) 
     | Binary(x, y) & Operator(op) -> sprintf "(%s %s %s)" (format x) op (format y) 
    | GroundTerm gt -> 
     match gt with 
     | Terminal(x) -> x 

cũng vậy, bạn nên tránh boxing nếu bạn muốn an toàn kiểu. Nếu bạn thực sự muốn cả hai trường hợp, hãy tạo hai mẫu. Hoặc, như được thực hiện ở đây, chỉ cần thực hiện một phép chiếu cho loại bạn cần sau này. Bằng cách này bạn tránh boxing và thay vào đó bạn trả lại những gì bạn cần cho in ấn.

+0

Ý tưởng thú vị. Tôi không biết liệu tôi có sử dụng điều này hay không, vì tôi muốn giữ liên minh của tôi đơn giản, nhưng đây có lẽ là giải pháp tốt nhất nếu tôi thực sự muốn an toàn loại. – luksan

2

Tôi nghĩ bạn có thể làm cho operator một chức năng bình thường thay vì một mẫu đang hoạt động. Bởi vì toán tử chỉ là một hàm cung cấp cho bạn một chuỗi toán tử cho một số expr, trong đó có unary, binaryterminal là các loại biểu thức và do đó có ý nghĩa đối với đối sánh mẫu trên chúng.

let operator expr = 
    match expr with 
    | Add(_) -> "+" 
    | Sub(_) | Neg(_) -> "-" 
    | Mult(_) -> "*" 
    | Div(_) -> "/" 
    | Pow(_) -> "**" 
    | Var(_) | Con(_) -> "" 


let rec format expr = 
    match expr with 
    | Unary(x) -> sprintf "%s(%s)" (operator expr) (format x) 
    | Binary(x, y) -> sprintf "(%s %s %s)" (format x) (operator expr) (format y) 
    | Terminal(x) -> string x 
+1

Trong trường hợp này, bạn từ bỏ một số tính an toàn thời gian biên dịch. Ngay sau khi bạn mở rộng 'Expr' bằng một trường hợp khác, toán tử' của bạn sẽ chỉ trả về kết quả (có khả năng sai) '" "' trái ngược với cảnh báo trình biên dịch, bạn cần mở rộng hàm của mình cho trường hợp. –

+1

Tôi đoán rằng có thể được giải quyết bằng cách không sử dụng '_' trường hợp và xác định từng trường hợp (xem câu trả lời cập nhật của tôi) – Ankur

+0

Có nó như là một chức năng là cách tôi bắt đầu. Nhưng đó vẫn không phải là thời gian an toàn biên dịch, bởi vì nó chỉ trả về một chuỗi rỗng mà nó không có ý nghĩa gì khi gọi hàm này. Tôi gần như sẽ có nó ném một ngoại lệ, như tôi ban đầu đã có, vì vậy ít nhất thì tôi biết nó được gọi là sai lầm. Nhưng ít nhất với cách của bạn (liệt kê từng loại một cách rõ ràng), tôi sẽ nhận được một cảnh báo để cập nhật các chức năng bất cứ khi nào tôi cập nhật công đoàn của tôi. – luksan

2

tôi tìm ra giải pháp tốt nhất là để cơ cấu lại ban đầu loại defintion của bạn:

type UnOp = Neg 
type BinOp = Add | Sub | Mul | Div | Pow 
type Expr = 
    | Int of int 
    | UnOp of UnOp * Expr 
    | BinOp of BinOp * Expr * Expr 

Tất cả các loại chức năng sau đó có thể được viết trên UnOpBinOp loại bao gồm việc lựa chọn nhà khai thác. Bạn thậm chí có thể muốn chia BinOp thành toán tử số học và so sánh trong tương lai.

Ví dụ: tôi đã sử dụng phương pháp này trong bài viết (không miễn phí) "Language-oriented programming: The Term-level Interpreter " (2008) trong F# Journal.