gpt4 book ai didi

c# - 具有在 C# 中可见的算术运算符的 F# 结构

转载 作者:太空宇宙 更新时间:2023-11-03 22:40:00 25 4
gpt4 key购买 nike

我正在尝试为 3 维点实现一个简单的结构。出于性能原因,我想将其作为结构。我想让它通用(至少对于 System.Int32System.Double ),并定义算术运算符。我计划在 F#/C# 混合解决方案中使用它。

为了简化代码,将所有内容都剥离为一维,以下是我的开头:

[<Struct>]
type Point<'T> =
val X: 'T
new(x) = { X = x}
static member inline (+) (p1: Point<'U> when 'U: (static member (+): 'U * 'U -> 'U), p2: Point<'U>): Point<'U> =
Point<_>(p1.X + p2.X)

(+) 上的类型参数运算符需要用 'U 而不是 'T 来编写,否则编译器会提示类型约束应该在 Point 的 'T 参数上.

这在 F# 中运行良好,我可以编写

let p1 = Point(2.0)
let sum = p1 + p1

在 C# 中:

var p = new Point<double>(1);
var sum = p + p;

那不编译,说 Operator + cannot by applied to operands of type Point<double> and Point<double>

如果我在 dotpeek 中查看编译后的 F# 代码,它说 +类型运算符 Point<T>有签名+(Point<???>,Point<???>): Point<???> .我认为这是因为我必须根据 'U 编写类型约束,并且可能还会触发 C# 编译器找不到运算符。

我可以通过定义一个带有运算符的 F# 模块来解决这个问题:

module Ops = 
let inline Add(p1, p2: Point<_>) = p1 + p2

有了它,我可以通过 Ops.Add(p1,p2) 在 C# 中进行加法运算- 但这显然不像 + 那样容易阅读运营商。

如果我尝试在顶层附加类型约束以进行添加,如下所示:

[<Struct>]
type Point<'T when 'T: (static member (+): 'T * 'T -> 'T)> =
val X: 'T
new(x) = { X = x}
static member inline (+) (p1: Point<'T>, p2: Point<'T>): Point<'T> =
Point<_>(p1.X + p2.X)

然后我在 new(x) = { X = x} 处收到编译器错误,说 This code is not sufficiently generic. The type variable ^T when ^T: (static member...) could not be generalized because it would escape its scope .

有没有办法暴露+ C# 编译器满意的运算符?

更新:运算符标记为 inline 的事实对结果没有重大影响:我可以定义

[<Struct>]
type Nothing<'T> =
val X: 'T
new(x) = { X = x}
static member inline (+) (p1: Nothing<'T>, p2: Nothing<'T>): Nothing<'T> =
Nothing<_>(p1.X)

并使用这个+运算符在 C# 中很好:

var p1 = new Nothing<double>(1);
var sum = p1 + p1;

最佳答案

inline 是 C# 不支持的 F# 编译器独有的功能。您必须(至少)明确定义 int32double 的运算符。

内联 函数由 F# 编译器内联,将通用参数替换为编译时已知 类型。 通用 函数通常 在运行时不可调用。一些异常(exception)情况,其中实现执行动态调度(参见例如 AdditionDynamic)确实在运行时工作但比它们的 inlined 等价物慢。另一个异常(exception)是非通用 inline 函数,其中 inline 元数据会被 C# 编译器忽略。

inline 具有传染性,所有调用 inline 函数的函数都必须 inline 自身在调用树中向上移动到被调用方的所有类型参数所在的位置在编译时已知。这解释了 ... would escape its scope 错误。因此,如果 inline 在程序集边界处结束,则这些函数不能从非 F# 项目中使用。

更新:你是对的,在没有实际使用 SRTP ( Statically Resolved Type Parameters ) 的情况下标记运算符 inline 没有效果:类型 T没有约束,所以不需要在编译时知道:

let inline Add(p1: Nothing<'T>, p2: Nothing<'T>) = Nothing<_>(p1.X)

有签名

val inline Add : p1:Nothing<'T> * p2:Nothing<'T> -> Nothing<'T>

一旦你真正使用了inline特性(这里是为了能够使用+),T就已知有一些限制:

let inline Add(p1: Nothing<_>, p2: Nothing<_>) = p1.X + p2.X

有签名

val inline Add :
p1:Nothing< ^a> * p2:Nothing< ^b> -> ^c
when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)

从 C# 的角度来看:

// normal (runtime) generics: works
let inline Add(p1: Nothing<'T>, p2: Nothing<'T>) = Nothing<_>(p1.X)
// SRTPs decalred only: works
let inline Add(p1: Nothing<(^T)>, p2: Nothing<(^T)>) = Nothing<_>(p1.X)
// SRTPs (requires member (+)), needs type annotation, slow (using AdditionDynamic, that is reflection), may fail at runtime (if no + operator)
let inline Add(p1: Nothing<(^T)>, p2: Nothing<(^T)>) = Nothing<_>(p1.X + p2.X)
// needs type annotation, guaranteed failure at runtime (no dynamic polyfill)
let inline Add(p1: Nothing<(^T)>, p2: Nothing<(^T)>) = Nothing<_>(p1.X %% p2.X)

现在,如果我们使用这些函数之一作为运算符,则只有非 SRTP 可以工作:

static member inline (*) (p1: Nothing<'T>, p2: Nothing<'T>) : Nothing<'T> = Nothing(p1.X) // fine

有 SRTP 声明就足够了:

static member inline (+) (p1: Nothing<(^a)>, p2: Nothing<(^a)>) : Nothing<(^a)> = Nothing(p1.X) // can not be used fom C#

这是为什么呢? C# 根本不支持泛型运算符(dotnet/csharplang 中有一些请求),而在 F# 中它们可以是内联d。事实上,如果我们查看反编译的源代码:

// introduces new generic parameter `a`
public static Nothing<a> operator +(Nothing<a> p1, Nothing<a> p2)
// uses T from containing struct
public static Nothing<T> operator *(Nothing<T> p1, Nothing<T> p2)

'T when 'T: (static member (+): 'T * 'T -> 'T) 约束是在结构上还是操作符上没有区别:我们'll always end with a generic operator.

关于c# - 具有在 C# 中可见的算术运算符的 F# 结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52779926/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com