F# 自定义类型提供程序 - 定义静态参数实例化函数多次调用



我有一个F#自定义类型提供程序(在本例中为CheckedRegexProvider);相关源是

[<TypeProvider>]
type public CheckedRegexProvider() as this =
inherit TypeProviderForNamespaces()
// Get the assembly and namespace used to house the provided types
let thisAssembly = Assembly.GetExecutingAssembly()
let rootNamespace = "Samples.FSharp.RegexTypeProvider"
let baseTy = typeof<obj>
let staticParams = [ProvidedStaticParameter("pattern", typeof<string>)]
let regexTy = ProvidedTypeDefinition(thisAssembly, rootNamespace, "RegexTyped", Some baseTy)
do regexTy.DefineStaticParameters(
parameters=staticParams, 
instantiationFunction=(fun typeName parameterValues ->
match parameterValues with ...

然后我有一个简单的测试项目,我将一些琐碎的代码放在.fs中

open Samples.FSharp.RegexTypeProvider
type T = RegexTyped< @"(?<AreaCode>^d{3})-(?<PhoneNumber>d{3}-d{4}$)">

令我困惑的是,实例化函数被调用的次数比我预期的要多得多。我认为只有当我更改静态参数(在本例中为"(?^\d{3})-(?\d{3}-\d{4}$)">)时才会调用该函数),而不是每次我在测试源中执行某些操作(例如按空格键)时,该 lambda 会连续调用两次。如果我真的更改参数,它会被调用两次或三次。

这当然对IDE(在我的例子中是Visual Studio)有相当大的影响,特别是因为该函数旨在提供可能需要扫描某些数据源以获取架构信息的类型。

然后,我尝试在单独的源模块文件中隔离类型提供程序调用(我们称之为 M1.fs),并在实际测试代码 (M2.fs) 中打开该模块。有了这个,lambda 仍然在每次触摸 M1.fs 时被调用,但当我在 M2.fs 中工作时,正如预期的那样,它根本没有被调用。

我要问的是:那些对实例化函数的连续重调用是否正确?这是设计使然吗?

如果是,为什么会这样?

类型提供程序在编译时调用,然后编译器使用它们在编译其余代码时提供的类型。

几乎每个处理 F# 代码的 IDE 都会通过在后台调用 F# 编译器服务来生成 AST(通常一次一个源文件),然后在该 AST 上执行操作以提供智能感知等操作,从而智能地执行此操作。

F# 编译器服务不会缓存以前编译的结果,因为所做的任何更改都可能在源文件之前和之后的其他位置产生影响。该声明的"之后"部分是出于显而易见的原因,但可能并不明显,为什么第 25 行的更改会影响第 10 行。其原因是类型推断:如果第 10 行是let a = Array.zeroCreate 1024,第 25 行是a.[0] <- 42,那么a的类型将被推断为int[]。如果将第 25 行更改为a.[0] <- "forty-two",则a的类型将被推断为string[],并且为 10 行构建的 AST 将有所不同。因此,F# 编译器服务每次调用时都会重新编译整个源文件

因此,每次编辑源文件时,F# 编译器服务都会重新编译该文件。如果该文件是包含类型提供程序定义的文件,则编译器必须实例化类型提供程序才能编译该文件,因此每次编译文件时都必须调用instantiationFunction

所以是的,你所看到的行为是设计使然。

最新更新