2009-11-27 39 views
10

Chúng tôi đang gettin 'lông ở đây. Tôi đã thử nghiệm một bó mã đồng bộ hóa cây trên các biểu diễn dữ liệu cụ thể, và bây giờ tôi cần trừu tượng nó để nó có thể chạy với bất kỳ nguồn và đích nào hỗ trợ các phương thức đúng. [Trong thực tế, đây sẽ là các nguồn như Documentum, phân cấp SQL và các hệ thống tệp; với các điểm đến như Solr và cửa hàng tham chiếu chéo SQL tùy chỉnh.]Buộc suy luận kiểu F # trên generics và giao diện để ở lại lỏng lẻo

Phần khó khăn là khi tôi đệ quy xuống một loại cây T và đồng bộ hóa thành một loại cây U, tại các tệp nhất định mà tôi cần làm "đồng bộ phụ" của loại thứ hai V đối với loại U tại nút hiện tại. (V đại diện cho cấu trúc phân cấp bên trong một tệp ...) Và công cụ suy luận loại trong F # đang hướng tôi đi vòng tròn trên này, ngay khi tôi cố gắng thêm đồng bộ phụ vào V.

Tôi đại diện cho điều này trong một số TreeComparison<'a,'b>, vì vậy nội dung trên dẫn đến một số TreeComparison<T,U> và so sánh phụ là TreeComparison<V,U>.

Vấn đề là, ngay sau khi tôi cung cấp một bê tông TreeComparison<V,'b> theo một trong các phương thức lớp, kiểu V truyền qua tất cả các suy luận, khi tôi muốn rằng loại đầu tiên tham số để ở lại chung (when 'a :> ITree). Có lẽ có một số cách gõ mà tôi có thể thực hiện trên giá trị TreeComparison<V,'b>? Hoặc, nhiều khả năng, suy luận đang thực sự nói với tôi điều gì đó vốn đã bị hỏng theo cách tôi đang nghĩ về vấn đề này.

Điều này thực sự khó khăn để nén, nhưng tôi muốn cung cấp mã làm việc bạn có thể dán vào tập lệnh và thử nghiệm, vì vậy có rất nhiều loại ở đầu ... công cụ cốt lõi là đúng ở cuối nếu bạn muốn bỏ qua. Hầu hết các so sánh thực tế và đệ quy trên các loại thông qua ITree đã bị cắt nhỏ bởi vì nó không cần thiết để xem các vấn đề suy luận mà tôi đang đập đầu của tôi chống lại.

open System 

type TreeState<'a,'b> = //' 
    | TreeNew of 'a 
    | TreeDeleted of 'b 
    | TreeBoth of 'a * 'b 

type TreeNodeType = TreeFolder | TreeFile | TreeSection 

type ITree = 
    abstract NodeType: TreeNodeType 
    abstract Path: string 
     with get, set 

type ITreeProvider<'a when 'a :> ITree> = //' 
    abstract Children : 'a -> 'a seq 
    abstract StateForPath : string -> 'a 

type ITreeWriterProvider<'a when 'a :> ITree> = //' 
    inherit ITreeProvider<'a> //' 
    abstract Create: ITree -> 'a //' 
    // In the real implementation, this supports: 
    // abstract AddChild : 'a -> unit 
    // abstract ModifyChild : 'a -> unit 
    // abstract DeleteChild : 'a -> unit 
    // abstract Commit : unit -> unit 

/// Comparison varies on two types and takes a provider for the first and a writer provider for the second. 
/// Then it synchronizes them. The sync code is added later because some of it is dependent on the concrete types. 
type TreeComparison<'a,'b when 'a :> ITree and 'b :> ITree> = 
    { 
    State: TreeState<'a,'b> //' 
    ATree: ITreeProvider<'a> //' 
    BTree: ITreeWriterProvider<'b> //' 
    } 

    static member Create(
         atree: ITreeProvider<'a>, 
         apath: string, 
         btree: ITreeWriterProvider<'b>, 
         bpath: string) = 
     { 
     State = TreeBoth (atree.StateForPath apath, btree.StateForPath bpath) 
     ATree = atree 
     BTree = btree 
     } 

    member tree.CreateSubtree<'c when 'c :> ITree> 
    (atree: ITreeProvider<'c>, apath: string, bpath: string) 
     : TreeComparison<'c,'b> = //' 
     TreeComparison.Create(atree, apath, tree.BTree, bpath) 

/// Some hyper-simplified state types: imagine each is for a different kind of heirarchal database structure or filesystem 
type T(data, path: string) = class 
    let mutable path = path 
    let rand = (new Random()).NextDouble 
    member x.Data = data 
    // In the real implementations, these would fetch the child nodes for this state instance 
    member x.Children() = Seq.empty<T> 

    interface ITree with 
    member tree.NodeType = 
     if rand() > 0.5 then TreeFolder 
     else TreeFile 
    member tree.Path 
     with get() = path 
     and set v = path <- v 
end 

type U(data, path: string) = class 
    inherit T(data, path) 
    member x.Children() = Seq.empty<U> 
end 

type V(data, path: string) = class 
    inherit T(data, path) 
    member x.Children() = Seq.empty<V> 
    interface ITree with 
    member tree.NodeType = TreeSection 
end 


// Now some classes to spin up and query for those state types [gross simplification makes these look pretty stupid] 
type TProvider() = class 
    interface ITreeProvider<T> with 
    member this.Children x = x.Children() 
    member this.StateForPath path = 
     new T("documentum", path) 
end 

type UProvider() = class 
    interface ITreeProvider<U> with 
    member this.Children x = x.Children() 
    member this.StateForPath path = 
     new U("solr", path) 
    interface ITreeWriterProvider<U> with 
    member this.Create t = 
     new U("whee", t.Path) 
end 

type VProvider(startTree: ITree, data: string) = class 
    interface ITreeProvider<V> with 
    member this.Children x = x.Children() 
    member this.StateForPath path = 
     new V(data, path) 
end 


type TreeComparison<'a,'b when 'a :> ITree and 'b :> ITree> with 
    member x.UpdateState (a:'a option) (b:'b option) = 
     { x with State = match a, b with 
         | None, None -> failwith "No state found in either A and B" 
         | Some a, None -> TreeNew a 
         | None, Some b -> TreeDeleted b 
         | Some a, Some b -> TreeBoth(a,b) } 

    member x.ACurrent = match x.State with TreeNew a | TreeBoth (a,_) -> Some a | _ -> None 
    member x.BCurrent = match x.State with TreeDeleted b | TreeBoth (_,b) -> Some b | _ -> None 

    member x.CreateBFromA = 
    match x.ACurrent with 
     | Some a -> x.BTree.Create a 
     | _ -> failwith "Cannot create B from null A node" 

    member x.Compare() = 
    // Actual implementation does a bunch of mumbo-jumbo to compare with a custom IComparable wrapper 
    //if not (x.ACurrent.Value = x.BCurrent.Value) then 
     x.SyncStep() 
    // And then some stuff to move the right way in the tree 


    member internal tree.UpdateRenditions (source: ITree) (target: ITree) = 
    let vp = new VProvider(source, source.Path) :> ITreeProvider<V> 
    let docTree = tree.CreateSubtree(vp, source.Path, target.Path) 
    docTree.Compare() 

    member internal tree.UpdateITree (source: ITree) (target: ITree) = 
    if not (source.NodeType = target.NodeType) then failwith "Nodes are incompatible types" 
    if not (target.Path = source.Path) then target.Path <- source.Path 
    if source.NodeType = TreeFile then tree.UpdateRenditions source target 

    member internal tree.SyncStep() = 
    match tree.State with 
    | TreeNew a  -> 
     let target = tree.CreateBFromA 
     tree.UpdateITree a target 
     //tree.BTree.AddChild target 
    | TreeBoth(a,b) -> 
     let target = b 
     tree.UpdateITree a target 
     //tree.BTree.ModifyChild target 
    | TreeDeleted b -> 
     () 
     //tree.BTree.DeleteChild b 

    member t.Sync() = 
    t.Compare() 
    //t.BTree.Commit() 


// Now I want to synchronize between a tree of type T and a tree of type U 

let pt = new TProvider() 
let ut = new UProvider() 

let c = TreeComparison.Create(pt, "/start", ut , "/path") 
c.Sync() 

Sự cố có thể xoay quanh CreateSubtree. Nếu bạn nhận xét ra một trong hai:

  1. Các docTree.Compare() dòng
  2. Các tree.UpdateITree cuộc gọi

và thay thế chúng với (), sau đó suy luận vẫn chung chung và mọi thứ đều đáng yêu.

Đây thực sự là một câu đố. Tôi đã thử di chuyển các hàm "so sánh" trong đoạn thứ hai ra khỏi kiểu và định nghĩa chúng như các hàm đệ quy; Tôi đã thử một triệu cách chú thích hoặc buộc phải nhập. Tôi chỉ không nhận được nó!

Giải pháp cuối cùng mà tôi đang xem xét là thực hiện triển khai hoàn toàn riêng biệt (và nhân bản) của loại so sánh và chức năng cho đồng bộ hóa phụ. Nhưng đó là xấu xí và khủng khiếp.

Cảm ơn bạn đã đọc sách này đến nay chưa! Sheesh!

+0

Lưu ý: Tôi không thực sự hiểu các trường hợp sử dụng cho các tham số kiểu được giải quyết tĩnh, như^a - có thể điều này xảy ra là một trong số chúng? –

Trả lời

16

tôi đã không phân tích mã, đủ để hiểu tại sao, nhưng thêm

member internal tree.SyncStep() : unit = 
          // ^^^^^^ 

dường như để sửa chữa nó.

EDIT

Xem thêm

Why does F# infer this type?

Understanding F# Value Restriction Errors

Unknown need for type annotation or cast

Phải mất kinh nghiệm để có được một sự hiểu biết rất sâu sắc về khả năng và giới hạn F # suy luận kiểu thuật toán của. Nhưng ví dụ này dường như nằm trong một lớp các vấn đề mọi người gặp phải khi họ làm những điều rất tiên tiến. Đối với các thành viên của một lớp, F thuật toán # suy luận làm điều gì đó như

  1. Nhìn vào tất cả các chữ ký rõ ràng thành viên để thiết lập một môi trường loại ban đầu cho tất cả các thành viên
  2. Đối với bất kỳ thành viên có chữ ký đầy đủ rõ ràng, sửa chữa các loại của chúng để chữ ký rõ ràng
  3. Bắt đầu đọc các đối tượng phương thức từ trên xuống dưới, từ trái sang phải (bạn sẽ gặp phải một số tham chiếu chuyển tiếp) có thể liên quan đến các biến loại chưa được giải quyết khi thực hiện điều này và có thể gây ra sự cố. .)
  4. Giải quyết tất cả các cơ quan thành viên đồng thời (... nhưng chúng tôi chưa thực hiện bất kỳ 'khái quát' nào, phần mà sẽ 'suy ra các tham số kiểu' thay vì 'sửa' lý thuyết có thể là hàm của 'một kiểu bất kỳ cụ thể nào mà trang web gọi đầu tiên của nó được sử dụng)
  5. Generalize (bất kỳ biến kiểu chưa được giải quyết nào trở thành biến kiểu suy luận thực tế của các phương pháp chung)

Điều đó có thể không chính xác; Tôi không biết nó đủ tốt để mô tả thuật toán, tôi chỉ có một cảm giác về nó. Bạn luôn có thể đọc thông số ngôn ngữ.

Điều thường xảy ra là bạn nhận được dấu đầu dòng 3 và buộc người giảm cân bắt đầu cố gắng giải quyết đồng thời/hạn chế tất cả các cơ quan phương pháp khi thực tế không cần thiết vì, ví dụ: có thể một số chức năng có loại cố định cụ thể dễ dàng. Giống như SyncStep là đơn vị-> đơn vị, nhưng F # không biết nó ở bước 3, vì chữ ký không rõ ràng, nó chỉ nói ok SyncStep có loại "unit -> 'a" cho một số loại chưa được giải quyết' a và thì bây giờ SyncStep bây giờ là không cần thiết phức tạp tất cả các giải quyết bằng cách giới thiệu một biến không cần thiết.

Cách tôi nhận thấy đây là cảnh báo đầu tiên (Cấu trúc này làm cho mã ít chung chung hơn được chỉ ra bởi chú thích kiểu. Biến kiểu 'a đã bị ràng buộc là kiểu' V ') nằm ở dòng cuối cùng của cơ thể của UpdateRenditions tại cuộc gọi đến docTree.Compare(). Bây giờ tôi biết rằng Compare() nên là đơn vị -> đơn vị. Vậy làm thế nào tôi có thể nhận được cảnh báo về chung chung-Ness ? Ah, ok, trình biên dịch không biết kiểu trả về là đơn vị tại thời điểm đó, do đó, nó phải điều rằng một cái gì đó là chung chung đó là không. Trong thực tế, tôi có thể đã thêm chú thích kiểu trả về để so sánh thay vì SyncStep - một trong hai cách hoạt động.

Dù sao, tôi đang rất dài. Tóm lại

  • nếu bạn có một chương trình nổi loại, nó sẽ 'làm việc'
  • đôi khi các chi tiết của thuật toán suy luận sẽ require 'thêm' một số chú thích ...trong trường hợp xấu nhất bạn có thể 'thêm tất cả' và sau đó 'trừ đi những thứ không cần thiết'
  • bằng cách sử dụng cảnh báo trình biên dịch và một số mô hình tinh thần của thuật toán suy luận, bạn có thể nhanh chóng hướng tới chú thích bị thiếu với trải nghiệm
  • thường là 'sửa' chỉ để thêm một chữ ký đầy đủ kiểu (bao gồm cả kiểu trả về) vào một số phương thức chính được 'khai báo muộn' nhưng 'được gọi là sớm' (giới thiệu tham chiếu chuyển tiếp giữa các tập hợp thành viên)

Hy vọng rằng sẽ giúp!

+0

OK. Tôi đang tăng tốc ở đây. Bạn đang nghiêm túc AWESOME, Brian. Tôi nợ bạn một chầu bia. Trên thực tế vào thời điểm này tôi có thể nợ bạn ăn tối và một bộ phim. Sheeeeesh. Nhưng tôi có một câu hỏi: LÀM THẾ NÀO Heck BẠN CÓ HÌNH R OUTNG RA? Haha. Tôi nghĩ rằng tôi đã cố gắng buộc chú thích MỌI CHỨC NĂNG KHÁC theo nhiều cách khác nhau, nhưng tôi chưa bao giờ nghĩ rằng một đơn vị -> chức năng đơn vị sẽ được hưởng lợi từ một chú thích. –

+3

Yup, thực hiện điều đó, các bài kiểm tra vượt qua các nhà cung cấp giả và mọi thứ. Bây giờ tôi chỉ cần ăn cắp bộ não của bạn. –

+0

Ok, tôi đã thêm rất nhiều câu trả lời để đề xuất các chiến lược bạn có thể sử dụng để giúp bản thân mình trong lần tiếp theo :) – Brian

3

Đây là một bài đăng cũ, nhưng đó là kết quả số 1 cho tìm kiếm của tôi. Tôi có một cái gì đó để thêm rằng có thể giúp bất cứ ai khác đấu tranh với suy luận kiểu như tôi (và OP) có.

Tôi thấy nó giúp suy luận suy luận như một số hàm mũ của cấu trúc các cuộc gọi hàm của bạn, những chữ ký mà các cuộc gọi đó có thể có, và những chữ ký nào mà chúng có thể không có. Nó rất quan trọng để đưa cả ba vào xem xét.

Chỉ cần cho đá, hãy xem xét chức năng này với ba biến: sqrt (2 * 2 * 3)

Ngay lập tức, nó rõ ràng rằng nó sẽ đơn giản hóa đến 2 lần số số vô tỉ, trong đó phải được làm tròn (như vậy, mua lại một mức độ vô hạn của sự không chính xác) để làm cho nó hữu ích trong cuộc sống hàng ngày.

Phiên bản F # tự cấp nguồn trở lại, làm phức tạp lỗi cho đến khi "làm tròn" lên đến đỉnh điểm thành các suy luận loại không mong muốn. Bởi vì những gì một loại có thể hoặc có thể không phải là một yếu tố vào phương trình này, nó không phải là luôn luôn có thể/dễ dàng để giải quyết vấn đề trực tiếp với các chú thích kiểu.

Bây giờ tưởng tượng rằng việc thêm một bổ sung hoàn hảo generic (tức là trung lập) chức năng giữa hai chức năng vấn đề, thay đổi phương trình của chúng tôi như thế này: sqrt (2 * 2 * 4)

Đột nhiên, kết quả là hoàn toàn hợp lý, sản xuất một giá trị hoàn toàn chính xác của 4. Ngược lại, sửa đổi các giá trị đầu tiên và thứ hai liên quan đến nghịch bằng 1 sẽ hoàn toàn không có gì để giúp chúng ta.

Đừng ngại sửa đổi cấu trúc nếu nó có khả năng tạo hoặc phá vỡ toàn bộ chương trình của bạn. Một chức năng bổ sung so với tất cả các hoops bạn phải nhảy qua (liên tục) uốn cong F # theo ý muốn của bạn là một mức giá rất nhỏ để trả tiền, và rất có thể là bạn có thể tìm cách để làm cho cấu trúc bổ sung hữu ích. Trong một số trường hợp, làm như trên có thể biến một chương trình rất, rất, rất tranh cãi thành một thiên thần nhỏ hoàn hảo, cho nhiều chức năng sắp tới.

Các vấn đề liên quan