简体   繁体   English

具有类型和接口继承的F#奇怪行为

[英]F# strange behavior with type and interface inheritance

Here is a piece of code that works after a couple of trials and errors, but I do not understand why it works in this particular way? 这是一段代码,经过几次试验和错误后才能运行,但我不明白为什么它以这种特殊方式工作? Is it by design and the rules are written in stone in some specs, or it works by incident in this case? 它是按设计制定的,规则是在一些规范中写成的,或者在这种情况下是偶然的吗? Comments in the code explain it all, but the question specifically is: 代码中的注释解释了这一切,但具体问题是:

Why the type B_from_A doesn't see that the slot for IA.Item is already implemented by the parent A , but then it allows only partial implementation of IA , so it actually sees that SharedMethod is already implemented in the parent. 为什么类型B_from_A没有看到,对于插槽IA.Item已经由母公司实现的A ,但随后只允许部分实现的IA ,所以它实际上是看到SharedMethod在父已经实施。

open System
open System.Collections.Generic

type IA = // only read methods
    abstract Item : int -> int with get
    abstract SharedMethod : int -> int

type IB = // allows in-place changes
    inherit IA
    abstract Item : int -> int with get, set

type IC = // immutable, returns new version with changes
    inherit IA
    abstract Set: int -> int -> IC

type A() =
    let dic = Dictionary<int,int>() // some complex internal data structure
    member internal this.Dic = dic
    member this.SharedMethod(i) = pown dic.[i] 2
    interface IA with
        member this.Item with get(i) = dic.[i]
        member this.SharedMethod(i) = this.SharedMethod(i) // custom operation on item

type B_from_A() =
    inherit A()
    // without this partial implementation I get an error:
    // Script1.fsx(111,18): error FS0361: The override 'get_Item : int -> int' 
    // implements more than one abstract slot, e.g. 'abstract member IB.Item : int -> int with get' 
    // and 'abstract member IA.Item : int -> int with get'
    interface IA with // partial interface implementation
        member this.Item with get(i) = this.Dic.[i] // required to remove error FS0361
        // !!! but there is no this.SharedMethod(i) here, so the type knows that this slot is 
        // implemented by parent A. Why it asks me to explicitly add Item get here?

    interface IB with
        member this.Item 
            with get(i) = this.Dic.[i] // implements more than one abstract slot without IA above
            and set i v = this.Dic.[i] <- v



type B() = // independent implementation
    let dic = Dictionary<int,int>()
    interface IA with
        member this.Item with get(i) = dic.[i]
        member this.SharedMethod(i) = pown dic.[i] 2
    interface IB with
        member this.Item  
            with get(i) = dic.[i]  
            and set i v = dic.[i] <- v
// If go from B to A, type A_from_B() won't be able to hide mutation methods in IB?
// It is more natural to add functionality than to hide or block it like some SCG ReadOnly collections do (e.g. throw on Add with InvalidOp)
// Therefore keep data structure inside A but add methods to change it inside B_from_A

Also where could I read fast and short about abstract slots and all the low level machinery inside F# polymorphism implementation? 另外,我在哪里可以快速简短地阅读抽象槽和F#多态实现中的所有低级机制?

This behaviour is completely defined by the spec 此行为完全由规范定义

Firstly from 8.14.3 Interface Implementations: 首先来自8.14.3接口实现:

Each member of an interface implementation is checked as follows: 接口实现的每个成员都按如下方式检查:

· The member must be an instance member definition. ·该成员必须是实例成员定义。

· Dispatch Slot Inference (§14.7) is applied. ·应用调度槽推理(第14.7节)。

· The member is checked under the assumption that the “this” variable has the enclosing type. ·在假设“this”变量具有封闭类型的情况下检查成员。

And then the quoted section: 然后是引用的部分:

14.7 Dispatch Slot Inference The F# compiler applies Dispatch Slot Inference to object expressions and type definitions before it processes their members. 14.7调度插槽推断F#编译器在处理其成员之前将Dispatch Slot Inference应用于对象表达式和类型定义。 For both object expressions and type definitions, the following are input to Dispatch Slot Inference: 对于对象表达式和类型定义,以下是对Dispatch Slot Inference的输入:

· A type ty0 that is being implemented. ·正在实施的类型ty0。

· A set of members override xM(arg1...argN). ·一组成员覆盖xM(arg1 ... argN)。

· A set of additional interface types ty1 ... tyn. ·一组附加接口类型ty1 ... tyn。

· A further set of members override xM(arg1...argN) for each tyi. ·另一组成员覆盖每个tyi的xM(arg1 ... argN)。

Dispatch slot inference associates each member with a unique abstract member or interface member that the collected types tyi define or inherit. Dispatch slot推断将每个成员与收集的类型定义或继承的唯一抽象成员或接口成员相关联。

As a result, each function can only work once - you can't get the double implementation that you want. 因此,每个函数只能工作一次 - 您无法获得所需的双重实现。

The behavior you describe occurs because IB inherits IA , both of them defining an Item property accessor. 您描述的行为发生是因为IB继承了IA ,它们都定义了Item属性访问器。 Therefore, the B_From_A.IB.Item property accessor implementation has two slots available. 因此, B_From_A.IB.Item属性访问器实现有两个可用的插槽。 However, when you add the B_From_A.IA.Item implementation, one slot is already occupied, so that no ambiguity remains for type inference -- only IB.Item remains available for implementation by B_From_A.IB.Item . 但是,当您添加B_From_A.IA.Item实现时,已经占用了一个插槽,因此类型推断没有任何歧义 - 只有IB.Item仍可供B_From_A.IB.Item实现。

Edit 编辑

For understanding the underlying mechanism, it is important to know that there are two different approaches of implementing a hierarchy of interfaces in F#: 为了理解底层机制,重要的是要知道在F#中实现接口层次结构有两种不同的方法:

// ----- A hierarchy of interfaces
type IFoo = abstract FooMember: int

type IBar =
    inherit IFoo
    abstract BarMember: int

// Approach 1: Implement IFoo "explicitly".
// In the object browser, you will see both IFoo and IBar as parents of FooBar.
type FooBar =
    interface IFoo with member this.FooMember = 0
    interface IBar with member this.BarMember = 0

// Approach 2: Implement IFoo "implicitly" (as part of IBar).
// In the object browser, you will only see IBar as parent of FooBar.
type FooBar =
    interface IBar with
        member this.FooMember = 0
        member this.BarMember = 0

Now, if both IFoo and IBar were to have a member with the exact same signature (eg, as in your example, an Item property accessor), and you implemented the member only via FooBar.IBar.Item , then a type inference ambiguity would necessarily arise, because it would be undefined behavior whether FooBar.IBar.Item should implement IBar.Item or IFoo.Item . 现在,如果IFooIBar都有一个具有完全相同签名的成员(例如,在您的示例中,是一个Item属性访问器),并且您只通过FooBar.IBar.Item实现了该成员,那么类型推断歧义将会必然会出现,因为它是未定义的行为, FooBar.IBar.Item是否应该实现IBar.ItemIFoo.Item

As a consequence, the answers to your questions are: 因此,您的问题的答案是:

  • "...why it works in this particular way": See above “......为什么它以这种特殊方式起作用”:见上文
  • "Is it by design": Yes, and also see @John's answer. “它是否符合设计”:是的,也看到@ John的回答。
  • "Why the type B_from_A doesn't see that the slot for IA.Item is already implemented by the parent A ": Your premise is incorrect. “为什么类型B_from_A没有看到,对于插槽IA.Item已经由母公司执行A ”:你的前提是不正确。 The compiler does see it, but it cannot know whether B_from_A.IB.Item should 编译器确实看到了它,但它无法知道B_from_A.IB.Item是否应该
    • reimplement the (in A) already-implemented IA.Item , or 重新实现 (在A中)已经实现的IA.Item ,或
    • implement IB.Item 实现 IB.Item
  • "But then it allows only partial implementation of IA , so it actually sees that SharedMethod is already implemented in the parent." “但是它只允许部分实现IA ,所以它实际上看到SharedMethod已经在父级中实现了。” As stated above, it sees that both IA.SharedMethod and IA.Item are already implemented by the parent A , but the ambiguity is not related to "seeing or not seeing" any pre-existing implementations in A . 如上所述,它认为这两个IA.SharedMethodIA.Item已经由母公司实现的A ,但不确定性是不相关的“看或不看”,在任何预先存在的实现A

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM