【问题标题】:Compiler unable to resolve type from shared 'header' file编译器无法从共享的“头”文件中解析类型
【发布时间】:2020-01-31 10:40:07
【问题描述】:

在 Common.fs 中:

namespace Bug
module Common =
    type SourceEntity = {
        id : int
        link : string
    }
    type ReleaseEntity = {
        id : int
        notes : string
    }

在 Release.fs 中

namespace Bug
open System
open Common
module Release =
    let cache = new Collections.Generic.Dictionary<int, ReleaseEntity>()
    let AddToCache(entity) =
        cache.Add(entity.id, entity)
        ()
    let AddRec() =
        let entity : ReleaseEntity = {
            id = 1
            notes = "Notes"
        }
        AddToCache(entity)

在 Source.fs 中

namespace Bug
open System
open Common
module Source =
    let Cache = new Collections.Generic.Dictionary<int, SourceEntity>()
    let AddToCache(entity) =
        Cache.Add(entity.id, entity) <<=== E R R O R
        ()
    let AddRec() =
        let ent : SourceEntity = {
            id = 1
            releases = "Releases"
        }
        AddToCache(ent)  <<=== E R R O R

Visual Studio 项目中按上述顺序包含的文件。

Source.fs 中报告的错误: 错误 FS0001 此表达式应具有类型 '源实体'
但这里有类型 '释放实体'

如果Common.fs中两种类型的顺序颠倒,Release.fs中报错,期望类型为ReleaseEntity但类型为SourceEntity。

任何想法为什么会发生此错误?

【问题讨论】:

    标签: f#


    【解决方案1】:

    这是记录字段名称的冲突(和阴影)。

    当您在Bug.Source.AddToCache 的主体中写入entity.id 时,编译器会使用您正在访问.id 字段这一事实来推断entity 的类型。哪些记录具有名为id 的字段?好吧,这两条记录可以,但编译器必须选择一条。如何?简单:最后一个优先。这称为“阴影”。

    为了消除选择的歧义,只需添加类型注释即可:

    let AddToCache(entity) = 
        Cache.Add(entity.id, entity)
        ()
    

    等等,为什么编译器不使用Cache.Add的类型来推断entity的类型?

    嗯,这只是 F# 的一个限制(或功能?)。编译是单通道,类型干扰从上到下,从左到右进行,没有双反。这允许编译器非常快速且非常可预测(看看你,Haskell)。

    但在这种情况下,这意味着当编译器看到entity 被用作Cache.Add 中的参数时,它已经决定了它必须是什么类型。

    【讨论】:

    • 谢谢。尽管转到了一个新文件和一个新模块,但我并没有完全理解编译器上下文被完整地携带。
    【解决方案2】:

    当您遇到类型错误时,请尝试考虑编译器是如何推断出该特定类型的。

    这里:

    let AddToCache(entity) =
        cache.Add(entity.id, entity)
        ()
    

    编译器能否知道哪个类型在entity 中有一个id 字段? 如果你输入了

    let entity = { id = 1; link  = "" }
    

    编译器会推断这是SourceEntity,因为只有SourceEntity 具有那些特定的记录字段。在 cache.Add(entity.id, entity) 中,编译器没有其他约束可以通过,除了它必须有一个 id 字段,所以它选择最后一个匹配类型 - 这就是你得到错误的原因。

    如果你将公共 id 字段重构为

    namespace Bug
    module Common =
        type SourceEntity = {
            source_id : int
            link : string
        }
        type ReleaseEntity = {
            release_id : int
            notes : string
        }
    

    你会发现错误消失了。

    解决方案

    所有解决方案都涉及将其限制为已知类型。

    最简单的就是添加类型注解:

    let AddToCache(entity: SourceEntity) =
    

    另一个是显式地解构它:

    let { SourceEntity.id = id } = entity
    Cache.Add(id, entity) 
    

    另一个是强制类型 - 这在这里不相关,但它可能会在未来变得有用:

    Cache.Add((entity :> SourceEntity).id, entity) 
    

    我推荐来自 F# 的 this article 以获得乐趣和类型推断的好处,以便很好地解释该过程。

    附言

    您实际上只需要一种类型注释。 其余的可以推断:)

    module Source =
        let Cache = new Collections.Generic.Dictionary<_, _>()
        let AddToCache (entity: SourceEntity) =
            Cache.Add(entity.id, entity) 
            ()
        let AddRec () =
            let ent = {
                id = 1
                link = ""
            }
            AddToCache(ent)  
    

    【讨论】:

    • 感谢您的详细回复。递归模块间依赖是我仍在努力解决的一个问题——可能需要重新组织类中的公共代码并使用继承——这是我想避免的。
    • 如果没有其他问题,您可以关闭问题。
    猜你喜欢
    • 2020-07-19
    • 2019-10-22
    • 1970-01-01
    • 2021-03-15
    • 2012-09-12
    • 1970-01-01
    • 1970-01-01
    • 2013-04-05
    • 2013-10-27
    相关资源
    最近更新 更多