最近有一些关于静态类型约束和内联的问题:
特别是两个陈述让我印象深刻:
“在.NET上的编译代码中无法以第一类方式编码此类型约束。但是,F#编译器可以在其内联函数的站点强制执行此约束,以便所有运算符用途在编译时解决。“
“...但是F#也可以在编译的程序集之间内联,因为内联是通过.NET元数据传达的。”
后者几乎似乎与前者相矛盾。我想知道为什么内联需要与静态类型约束功能相关。难道F#编译器无法正常运行(我相信)C ++模板和.NET泛型可以在编译时根据需要生成类型特定的可重用函数吗?
例如:
多态定义:
let add (lhs:^a) (rhs:^a) = lhs + rhs
用法:
let a = add 2 3
let b = add 2. 3.
let b = add 4. 5.
编译器生成的添加功能:
let add_int (lhs:int) (rhs:int) = lhs + rhs
let add_float (lhs:float) (rhs:float) = lhs + rhs
编译器重写用法:
let a = add_int 2 3
let b = add_float 2. 3.
let c = add_float 4. 5.
我不是编译器作家也不是语言设计师,虽然这些主题对我很感兴趣,所以请指出我的天真。
答案 0 :(得分:12)
两位。
首先“这样的事情不能正常工作吗?”是。 “单独编译”通常被视为布尔值,但它实际上是一个连续体。通常,对于静态类型语言的“单独编译”意味着我只需要知道类/方法的接口/签名类型,然后就可以在另一个模块/程序集中针对该实体进行编译,并且成功并生成良好的代码,或者否则会生成有关错误的有用诊断。关键的想法是“类型签名”是“基本约束的简洁总结”。
你可以将它按比例滑动到光谱的另一端,然后像F#inline或C ++模板那样执行类似鸭子的事情,其中基本上“如果你将这些数据类型插入到代码体中这个方法,会编译吗?“这是可行的,但它通常会因为编译速度慢而在事情失败时提供较差的诊断。这些后果源于缺乏“简洁的摘要”,这是“单独汇编”中“独立”的典型工具。
(从评估效用/可用性的完全不同的轴来看,内联/ C ++模板模型“非常灵活”和“非常复杂”,而典型的类型系统内容“灵活性较差但通常足以表达大多数抽象“和”非常简单“。)
无论如何,所以一切皆有可能,但是如果你做的事情比“类型/泛型/类型类”的一些标准的易于理解的机制更“灵活”,那么在编译速度和编译时,你往往会陷入困境。错误诊断质量。因此,在F#中,当您通过inline
特别请求时,该机制才会启动。
其次,当.NET元数据无法表达这些约束时,F#如何设法“跨组件内联”?简单来说,带有任何inline
内容的F#程序集包含额外的F#特定元数据(除了通常的.NET元数据之外),它基本上代表“F#代码主体”,因此它可以在引用的调用站点处内联部件。 F#特定的元数据嵌入.NET程序集中的“资源”。在F#PowerPack中,有一个元数据读取器模块,使您能够“反映”一些额外的F#元数据。 (在FSharp.Core的情况下,这个额外的元数据被分解为FSharp.Core.sigdata和FSharp.Core.optdata文件,而不是嵌入到FSharp.Core.dll运行时程序集中。这使F#运行时更小,因为你在设计时只需要sigdata / optdata。)
答案 1 :(得分:5)
我认为在没有内联的情况下允许静态成员约束在技术上是可行的。从历史上看,我认为该功能主要用于标准数学运算符,但它在更新版本中变得更强大,更有用。
但是,有几个很好的理由可能导致您不想经常使用它:
类型签名的可读性如果所有通用方法都被解释为具有“帽子类型”的方法,那么类型签名的可读性将受到影响,因为每个类型参数都会有大量的方法需要实施的。
互操作性其他.NET语言无法调用声明为inline
的函数/方法。由于互操作性是F#非常关注的问题,因此选择通常的通用方法(可以从C#中使用)似乎是合理的。
按照你的建议(和C ++ / CLI编译器一样)生成专门的函数将是F#实现(内联)的另一种替代方法,但它可能需要更多的努力。在任何情况下,您仍然需要以某种方式标记函数,因此在F#中编写泛型函数的自然方式是使用标准泛型。
最后 - 解释两个陈述中的矛盾。我相信“inline通过.NET元数据传达”实际上意味着有关内联的信息以某种二进制格式存储在.NET资源中。这意味着它是.NET程序集的一部分,但只有F#才能理解它。