[英]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
实现。
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
. 现在,如果
IFoo
和IBar
都有一个具有完全相同签名的成员(例如,在您的示例中,是一个Item
属性访问器),并且您只通过FooBar.IBar.Item
实现了该成员,那么类型推断歧义将会必然会出现,因为它是未定义的行为, FooBar.IBar.Item
是否应该实现IBar.Item
或IFoo.Item
。
As a consequence, the answers to your questions are: 因此,您的问题的答案是:
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
是否应该
IA.Item
, or IA.Item
,或 IB.Item
IB.Item
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.SharedMethod
和IA.Item
已经由母公司实现的A
,但不确定性是不相关的“看或不看”,在任何预先存在的实现A
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.