簡體   English   中英

強制泛型和接口上的F#類型推斷保持松散

[英]Forcing F# type inference on generics and interfaces to stay loose

我們在這里毛茸茸的。 我已經在數據的具體表示形式上測試了一堆樹同步代碼,現在我需要對其進行抽象,以便它可以與支持正確方法的任何源和目標一起運行。 [在實踐中,這將是諸如Documentum,SQL層次結構和文件系統之類的資源; 目的地,例如Solr和自定義SQL交叉引用存儲。]

棘手的部分是,當我遞歸T類型的樹並同步到U類型的樹時,在某些文件上,我需要對當前的U類型執行第二種V的“子同步”節點。 V表示文件內部的層次結構...)並且,當我嘗試將子同步添加到V ,F#中的類型推理引擎正以此為動力。

我用TreeComparison<'a,'b> ,因此上述內容導致TreeComparison<T,U>TreeComparison<V,U>的子比較。

問題是,當我提供一個具體的TreeComparison<V,'b>在的類方法之一, V通過所有所述推斷的類型傳播,當我想的是第一種類型的參數留通用的( when 'a :> ITree )。 也許可以在TreeComparison<V,'b>值上進行一些鍵入? 或者,更有可能的是,推論實際上是在告訴我某些問題在我思考此問題的方式上固有地被打破。

壓縮起來確實很棘手,但是我想提供可以粘貼到腳本中並進行實驗的工作代碼,因此開始時有很多類型……如果您想跳過,核心內容就在最后。 通過ITree進行的跨類型的大多數實際比較和遞歸已被砍掉,因為沒有必要看到我大吃一驚的推理問題。

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()

該問題可能與CreateSubtree有關。 如果您將其中任何一個注釋掉:

  1. docTree.Compare()
  2. tree.UpdateITree調用

並將其替換為() ,則推論保持通用,一切都很美好。

這真是一個難題。 我嘗試將第二個塊中的“比較”函數移出類型,並將其定義為遞歸函數。 我已經嘗試了百萬種注釋或強制鍵入的方法。 我就是不明白!

我正在考慮的最后一個解決方案是對子同步的比較類型和函數進行完全獨立(重復)的實現。 但這是丑陋和可怕的。

感謝您閱讀本文! 噓!

我沒有分析代碼足以找出原因,但是添加了

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

似乎解決了。

編輯

也可以看看

為什么F#會推斷這種類型?

了解F#值限制錯誤

未知需要類型注釋或強制轉換

需要經驗才能對F#類型推斷算法的功能和局限性有很深的了解。 但是這個例子似乎是人們在做非常高級的事情時遇到的一類問題。 對於類的成員,F#推理算法的作用類似於

  1. 查看所有成員顯式簽名以為所有成員設置初始類型環境
  2. 對於具有完全顯式簽名的任何成員,請將其類型固定為顯式簽名
  3. 從上到下,從左到右開始閱讀方法主體(這樣做時,您會遇到一些“前向引用”,其中可能涉及未解決的類型變量,並且可能會引起麻煩,因為...)
  4. 同時解決所有成員體(...,但我們尚未進行任何“概括”,該部分將“推斷類型參數”而不是“固定”理論上可能是“ a的函數,無論具體類型如何”)它使用的第一個呼叫站點)
  5. 通用化(任何剩余的未解決類型變量將成為通用方法的實際推斷類型變量)

那可能並不完全正確。 我對它的描述不夠了解,我只是對它有所了解。 您可以隨時閱讀語言規范。

經常發生的事情是您到達第3條,並迫使推理者開始嘗試同時解決/約束所有方法主體,而實際上這是不必要的,因為例如某些函數具有簡單的具體固定類型。 就像SyncStep是unit-> unit,但是F#在步驟3中還不知道,因為簽名不是明確的,它只是說ok SyncStep的類型為“ unit->'a”,而某些尚未解決的類型為'a和那么現在SyncStep現在不必要地引入了一個不必要的變量,從而使所有求解變得不必要了。

我發現的方式是,第一個警告(此構造導致代碼的通用性低於類型注釋所指示的類型。類型變量'a已被約束為類型'V')位於主體的最后一行調用docTree.Compare()的UpdateRenditions。 現在我知道Compare()應該是unit-> unit。 所以,我怎么可能會得到一個有關通用岬警告? 嗯,好了,編譯器當時還不知道返回類型是unit,所以一定是通用的東西不是。 實際上,我可以將返回類型注釋添加到Compare中,而不是SyncStep-兩種方法都可以。

無論如何,我一直很long。 總結一下

  • 如果您有一個很好的程序,它應該“起作用”
  • 有時,推理算法的細節將需要一些“額外”注釋...在最壞的情況下,您可以“全部添加”,然后“減去不必要的注釋”
  • 通過使用編譯器警告和推理算法的某些思維模型,您可以迅速地將缺少經驗的注釋轉向
  • 通常,“修復”只是將一個完整類型的簽名(包括返回類型)添加到某個“延遲聲明”但又稱為“早期調用”的關鍵方法中(在成員集合中引入前向引用)

希望有幫助!

這是一篇舊文章,但這是我搜索的第一名結果。 我要補充一點,它可以像我(和OP)一樣幫助其他在類型推斷方面苦苦掙扎的人。

我發現將推論視為函數調用結構的某些指數函數,這些調用可能具有的簽名以及它們可能沒有的簽名會有所幫助。 將這三個因素都考慮進去非常重要。

僅針對踢球,請考慮具有以下三個變量的此函數:sqrt(2 * 2 * 3)

顯然,它將立即簡化為非理性數字的2倍,必須對其進行四舍五入(從而獲得無限的不精確度),才能使其在日常生活中有用。

F#版本會反饋給自身,加劇錯誤,直到“舍入”最終變為不良的類型推斷。 由於類型可能會或可能不會成為此方程式的一個因素,因此使用類型注釋直接解決問題並不總是/容易。

現在假設在兩個問題函數之間添加一個額外的完全泛型(即中性)函數,將我們的方程式更改為:sqrt(2 * 2 * 4)

突然之間,結果是完全合理的,產生了一個完全准確的值4。相反,將與反向相關的第一和第二值修改為1絕對沒有任何幫助。

如果結構有可能構成或破壞整個程序,請不要害怕對其進行修改。 一個額外的功能與您必須跳過(連續)彎曲F#到您的意志的所有箍相比,付出的代價很小,而且您有可能找到一種使額外結構有用的方法。 在某些情況下,執行上述操作可以將一個非常非常具爭議性的程序變成一個完美的小天使,以供將來使用許多功能。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM