Структура F # с арифметическими операторами, видимыми в C # - PullRequest
0 голосов
/ 12 октября 2018

Я пытаюсь реализовать простую структуру для трехмерных точек.Из соображений производительности я хотел бы иметь это как структуру.Я хотел бы, чтобы он был общим (по крайней мере, для System.Int32 и System.Double), и чтобы были определены арифметические операторы.Я планирую использовать это в смешанном решении F # / C #.

Для упрощения кода все сводится к 1D, вот что я начал с:

[<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, в противном случае компилятор жалуется, что ограничения типа должны быть на аргументе 'T Point.

Это прекрасно работает в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>

Если я смотрю на скомпилированный код F # вdotpeek, это говорит о том, что оператор + для типа Point<T> имеет подпись +(Point<???>,Point<???>): Point<???>.Я предполагаю, что это результат того, что мне пришлось написать ограничение типа в терминах 'U - и, вероятно, это также приведет к тому, что компилятор C # не найдет оператор.

Я могу обойти эту проблему, определив модуль F #с операторами:

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

С этим я могу добавлять в C # с помощью Ops.Add(p1,p2) - но это, очевидно, не так легко читается, как оператор +.

Если я попытаюсьприкрепите ограничение типа для добавления на верхнем уровне, например:

[<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;

1 Ответ

0 голосов
/ 12 октября 2018

inline - это особенность, уникальная для компилятора F #, которую C # не поддерживает.Вам нужно будет определить (по крайней мере) операторы для int32 и double явно.

inline функции будут встроены компилятором F #, заменив универсальный параметр (и) на compile-Время известных типов.Общая функция - это , в общем не вызывается во время выполнения.Несколько исключений, когда реализация выполняет динамическую диспетчеризацию (см., Например, AdditionDynamic ) do работает во время выполнения, но медленнее, чем их inline d эквиваленты.Другое исключение - неуниверсальные функции inline, где метаданные inline просто игнорируются компилятором C #.

inline заразна, все функции, вызывающие функции inline, сами должны быть inlineвверх по дереву вызовов, где все параметры типа для вызываемого абонента известны во время компиляции .Это объясняет ошибку ... would escape its scope.Следовательно, если inline заканчивается на границах сборки, эти функции не могут использоваться в проектах, отличных от F #.

Обновление : Вы правы, отмечая оператор inline без фактического использованияSRTP ( Статически разрешенные параметры типа ) не имеют никакого эффекта: тип 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) 

Теперь, если мы используем одну из этих функций в качестве операторов, толькоnon-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 # они могут быть inline 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) на структуру или оператор не имеет значения: мы всегда получим универсальный оператор .

...