2010-02-06 24 views
9

Tôi có một hàm cà ri mà tôi muốn nó để hỗ trợ các loại khác nhau của các thông số, mà không phải là trên một mối quan hệ thừa kế:F # khớp mẫu trên các loại của các bộ

type MyType1 = A | B of float 
type MyType2 = C | D of int 

Những gì tôi cố gắng làm là:

let func x y = 
    match (x, y) with 
    | :? Tuple<MyType1, MyType1> -> "1, 1" 
    | _ -> "..." 

Tuy nhiên điều này là không thể. F # than phiền:

Loại '' a * 'b' không có bất kỳ loại phụ thích hợp nào và không thể được sử dụng làm nguồn kiểm tra loại hoặc cưỡng chế thời gian.

Cách thanh lịch để làm điều này là gì?

EDIT: Hãy để tôi cố làm rõ điều này.

Tôi có hai loại tương tự nhưng khác biệt. Tôi có thể rất dễ dàng chuyển đổi loại này sang loại khác. Tôi muốn xác định một hoạt động nhị phân sẽ hành động trên các thực thể của các loại, nhưng tôi muốn để lộ một hoạt động duy nhất cho khách hàng.

Đó là, thay vì cung cấp:

let op11 (x : MyType1) (y : MyType1) = // do something useful 
let op12 (x : MyType1) (y : MyType2) = 
    // convert y to MyType1 
    let y' = // ... 
    // dispatch to op11 
    op11 x y' 
let op21 (x : MyType2) (y : MyType1) = // similar 
let op22 (x : MyType2) (y : MyType2) = // similar 

những gì tôi muốn là để lộ một hàm duy nhất để mã khách hàng:

let op (x : obj) (y : obj) = // ... 

này giống như mô phỏng hành vi của phương pháp quá tải, nhưng với các chức năng được thu thập.

+0

Type-thử nghiệm trong F #? Có vài điều sai sót ở đây. Chính xác những gì bạn đang cố gắng để làm? – Juliet

+0

Tôi không hiểu những gì bạn đang yêu cầu - chữ ký của 'func' là gì (các loại 'x' và 'y') là gì? – Brian

Trả lời

14

Mã của bạn không hoạt động, vì F # khái quát loại đối số cho thông số loại. Tôi nghĩ rằng bạn không thể kiểm tra động một loại 'a * 'b có thể được chuyển đổi thành loại MyType1 * MyType2 (mặc dù điều này hơi khó hiểu với tôi). Trong mọi trường hợp, bạn có thể viết một hàm có hai tham số kiểu obj và kiểm tra riêng từng sử dụng hai :? mẫu:

type MyType1 = A | B of float 
type MyType2 = C | D of int 

let func (x:obj) (y:obj) = 
    match (x, y) with 
    | (:? MyType1 as x1), (:? MyType1 as x2) -> 
     printfn "%A %A" x1 x2 
    | _ -> 
     printfn "something else" 

func A (B 3.0) // A B 3.0 
func A (D 42) // something else 

Dù sao, nó sẽ là thú vị để biết tại sao bạn muốn làm điều này? Có thể có một giải pháp tốt hơn ...

EDIT (2) Vì vậy, từ tất cả 4 kết hợp hai yếu tố T1T2, bạn muốn chức năng mà có thể mất 3. Có phải đó là đúng (T1 * T1, T1 * T2T2 * T2)? Trong trường hợp đó, bạn không thể viết hàm curried hoàn toàn an toàn, vì kiểu đối số thứ hai sẽ "phụ thuộc" vào loại đối số đầu tiên (nếu đối số đầu tiên có loại T2, thì đối số thứ hai cũng phải là T2 (nếu không nó cũng có thể là T1)).

Bạn có thể viết một hàm an toàn phi cà ri mà phải mất một đối số của các loại sau đây:

type MyArg = Comb1 of T1 * T1 | Comb2 of T1 * T2 | Comb3 of T2 * T2 

Kiểu của hàm sẽ là MyArg -> string. Nếu bạn muốn có chức năng được thu thập, bạn có thể xác định loại cho phép bạn sử dụng hoặc T1 hoặc T2 làm đối số thứ nhất và thứ hai.

type MyArg = First of T1 | Second of T2 

Sau đó, hàm được kết hợp sẽ là MyArg -> MyArg -> string. Nhưng lưu ý rằng nếu không được phép kết hợp một loại đối số (nếu tôi hiểu chính xác bạn, không nên cho phép T2 * T1). Trong trường hợp này, chức năng của bạn sẽ chỉ cần ném một ngoại lệ hoặc một cái gì đó như thế.

+1

Excelent! Đó là những gì tôi đang tìm kiếm, một cách để sử dụng hai mô hình ':? '. Cảm ơn. –

+0

Lý do: đơn giản hóa rất nhiều, tôi có điều này: 'type data = int',' type MyType = A | B dữ liệu | C dữ liệu * dữ liệu'. Tôi muốn một 'makeC: ??? -> ??? -> MyType', sẽ mất 2 'dữ liệu' hoặc 'dữ liệu' và' B (x) 'hoặc hai' B (x) 's. –

+0

Về 'makeC', bạn không thực sự muốn chức năng đó. – Brian

1

Điều này có mùi rất cá, bạn nên mô tả bối cảnh vấn đề lớn hơn vì có vẻ như bạn không nên ở trong tình huống này. Điều đó nói rằng:

type MyType1 = A | B of float 
type MyType2 = C | D of int 

// imagine this adds floats, and C -> A (=0.0) and D -> B 
let DoIt x y = 
    match x, y with 
    | A, A -> 0.0 
    | A, B z -> z 
    | B z, A -> z 
    | B z1, B z2 -> z1 + z2 

let Convert x = 
    match x with 
    | C -> A 
    | D i -> B (float i) 

let Func (x:obj) (y:obj) = 
    match x, y with 
    | (:? MyType2 as xx), (:? MyType2 as yy) -> DoIt (Convert xx) (Convert yy) 
    | (:? MyType1 as xx), (:? MyType2 as yy) -> DoIt xx (Convert yy)  
    | (:? MyType2 as xx), (:? MyType1 as yy) -> DoIt (Convert xx) yy 
    | (:? MyType1 as xx), (:? MyType1 as yy) -> DoIt xx yy  
    | _ -> failwith "bad args" 
+0

Ngữ cảnh lớn hơn: Tôi đang đối phó với "tập hợp điểm", có thể là trống, các điểm đơn lẻ, đường thẳng hoặc công đoàn của tất cả những thứ đó: 'type PSet = Empty | Một điểm | Dòng điểm * hướng | Liên kết danh sách PSet'. Tôi muốn có thể thực hiện một dòng từ 2 điểm, chúng có thể được đưa ra dưới dạng hai 'điểm' hoặc được bao bọc bên trong một' điểm duy nhất '. –

+0

Phải. Chức năng 'makeLine' của bạn có thể mất hai điểm, và các khách hàng có 'Độc thân' nên 'unwrap' chúng trước khi gọi 'makeLine'. Hoặc có thể sử dụng quá tải hoặc có chức năng riêng biệt (ví dụ: 'makeLineFromSingles'). Đừng làm những gì bạn đang làm, bạn đang loại bỏ an toàn kiểu tĩnh, và làm cho mã không cần thiết phức tạp. Từ một nền tảng OO, thật hấp dẫn để tạo ra một loạt các tình trạng quá tải rằng 'xoa bóp' phần nào dữ liệu tương tự để đưa nó vào đúng hình dạng. Chiến đấu với xu hướng đó và bạn sẽ tốt hơn. – Brian

+0

Lol. Vâng, bạn nói đúng, tôi đang nghĩ về OO (như tôi đã làm cho những năm maaaaany) quá nhiều. Nó có thể là lành mạnh để tìm hiểu để làm những việc theo những cách khác! –

3

Tôi có hai giống nhau, nhưng rõ rệt, loại. Tôi có thể dễ dàng chuyển đổi một loại sang loại khác. Tôi muốn xác định hoạt động nhị phân sẽ hoạt động trên đối tượng thuộc các loại đó, nhưng tôi muốn để hiển thị một hoạt động cho ứng dụng .

Chức năng này nên quyết định xem số nào trong số gọi opXY, downcasting đúng cách loại.

Đây là một trong những trường hợp câu trả lời đúng không phải là "dưới đây là cách bạn thực hiện", nhưng thay vào đó "không làm như vậy". Không có lợi thế thực sự khi sử dụng F # nếu bạn không gắn bó với thành ngữ và trình kiểm tra kiểu của nó.

Từ quan điểm của tôi, nếu loại của bạn là như vậy tương tự, họ cần được sáp nhập vào cùng loại:

type MyType = A | B of float | C | D of int 

Chặn điều đó, bạn có thể quấn hai loại của bạn trong loại khác:

type composite = 
    | MyType1Tuple of MyType1 * MyType1 
    | MyType2Tuple of MyType2 * MyType2 

Một lớp khác của indirection không bao giờ làm tổn thương bất cứ ai. Nhưng ít nhất bây giờ khách hàng của bạn có thể quấn các đối tượng với nhau mà không làm mất an toàn loại.

Và chặn tất cả điều đó, hiển thị hai phương pháp riêng biệt cho các loại khác nhau của bạn.

6

Thực chất, có ba cách khác nhau để thực hiện việc này.

Đầu tiên là để hy sinh gõ tĩnh bởi upcasting để obj như bạn đề nghị:

let func x y = 
    match box x, box y with 
    | (:? MyType1 as x), (:? MyType1 as y) -> 
     ... 

Đây là hầu như luôn luôn là một giải pháp khủng khiếp vì nó kết quả trong việc giới thiệu không cần thiết thời gian chạy loại kiểm tra:

| _ -> invalidArg "x" "Run-time type error" 

chỉ đặt Tôi đã thấy công việc này là mã thư viện được thiết kế đặc biệt cho người dùng gọi từ các phiên tương tác F # nơi biên dịch và thời gian chạy loại er rors hiệu quả xảy ra cùng một lúc và gõ động có thể ngắn gọn hơn. Ví dụ: thư viện F# for Visualization của chúng tôi cho phép người dùng cố gắng hình dung mọi giá trị của bất kỳ loại nào bằng cách sử dụng kỹ thuật này để gọi thói quen trực quan hóa tùy chỉnh cho các loại (được biết) khác nhau (read more).

Thứ hai là để thay thế hai loại riêng biệt của bạn với một loại duy nhất:

type MyType1 = A | B of float | C | D of int 

Giải pháp thứ ba là để giới thiệu một loại mới mà thống nhất chỉ những hai loại:

type MyType = MyType1 of MyType1 | MyType2 of MyType2 
Các vấn đề liên quan