Добавление ограничения-члена к параметру встроенной функции вызывает FS0752 для доступа к массиву

Я снова и снова читал официальные документы Microsoft об ограничениях типов, но не могу понять, почему этот код не компилируется:

let inline transform<'A, 'a when 'A : (member Item : int -> float)> (a: 'A) : 'a =
    a.[0]

ошибка FS0752: оператор expr.[idx] использовался для объекта неопределенного типа на основе информации, предшествующей этой точке программы. Рассмотрите возможность добавления дополнительных ограничений типа

И с:

let inline transform<'A, 'a when 'A : (member f : int -> float)> (a: 'A) : 'a =
    a.f(0)

ошибка FS0072: поиск по объекту неопределенного типа на основе информации до этой программной точки. Аннотация типа может потребоваться до этой программной точки, чтобы ограничить тип объекта. Это может позволить разрешить поиск.

Очевидно, я не понимал, как использовать ограничение члена с обобщениями в F#. Общая проблема, с которой я сталкиваюсь, заключается в том, что я хочу сделать универсальные функции над "векторными" типами, такими как стандартные float[], или же Vector<float> от MathNet.Numerics или даже DV из пакета DiffSharp. Прямо сейчас я должен получить специальную функцию для каждого типа, например (полный код):

#I ".paket/load"
#load "mathnet.numerics.fsharp.fsx"
#load "diffsharp.fsx"

open DiffSharp.AD.Float64

open MathNet.Numerics
open MathNet.Numerics.LinearAlgebra
open MathNet.Numerics.LinearAlgebra.Double

let l1 = 4.5
let l2 = 2.5

let a0 = [1.1; -0.9]

let inline transformVec (a:Vector<float>) =
    let x1, y1 = l1 * cos a.[0], l1 * sin a.[0]
    let x2, y2 = x1 + l2 * cos (a.[0] + a.[1]), y1 + l2 * sin (a.[0] + a.[1])
    vector [x1; y1; x2; y2]

let inline transformDV (a:DV) =
    let x1, y1 = l1 * cos a.[0], l1 * sin a.[0]
    let x2, y2 = x1 + l2 * cos (a.[0] + a.[1]), y1 + l2 * sin (a.[0] + a.[1])
    toDV [x1; y1; x2; y2]

Как видите, эти функции выполняют одни и те же функции, но работают с разными типами.

Я хотел бы получить общую функцию, такую ​​как (не рабочий код):

let inline transform<'A, 'a when 'A : (member Item : int -> 'a)> (toExt : 'a list -> 'A) (a: 'A) : 'A =
    let x1, y1 = l1 * cos a.[0], l1 * sin a.[0]
    let x2, y2 = x1 + l2 * cos (a.[0] + a.[1]), y1 + l2 * sin (a.[0] + a.[1])
    toExt [x1; y1; x2; y2]

let transformVec = transform vector
let transformDV = transform toDV  

Что мне не хватает?


Редактировать: у меня есть половина работы с Mathnet.Numerics

let inline transform (toExt : 'a list -> 'A) (a: 'A) : 'A = 
    let inline get i : 'a = (^A : (member get_Item: int -> 'a) a,i)
    let x1, y1 = l1 * cos (get(0)), l1 * sin (get(0))
    let x2, y2 = x1 + l2 * cos (get(0) + get(1)), y1 + l2 * sin (get(0) + get(1))
    toExt [x1; y1; x2; y2]

(transform vector) (vector a0)

Потому что это обеспечивает 'a (предупреждение FS0064) быть floatчего я не хочу... (DV от DiffSharp возвращается D введите на get_Itemне float.)

заменить декларацию на

let inline transform<'a> (toExt : 'a list -> 'A) (a: 'A) : 'A = 

делает компилятор каркает:

ошибка FS0001: объявленный параметр типа "a" не может использоваться здесь, поскольку параметр типа не может быть разрешен во время компиляции

1 ответ

Решение

Вам нужно позвонить участнику Item как это:

let inline transform (a: 'A) : 'a = (^A : (member get_Item: _ -> _) a, 0)

Однако вы получите предупреждение,

~vs72B.fsx(2,5): warning FS0077: Member constraints with the name 'get_Item' are given special status by the F# compiler as certain .NET types are implicitly augmented with this member. This may result in runtime failures if you attempt to invoke the member constraint from your own code.

потому что некоторые примитивные типы используют "симулированные члены". Так что для списка это будет работать:

transform ["element"]
// val it : string = "element"

но не для массивов

transform [|"element"|]
System.NotSupportedException: Specified method is not supported.
at <StartupCode$FSI_0009>.$FSI_0009.main@()
Stopped due to error

Это потому, что компилятор F# делает вид, что массивы имеют этот член, но на самом деле их нет.

Если это проблема, вы можете использовать более сложное решение с перегрузками для добавления специальных реализаций для конкретных типов, что не так просто, но я могу показать вам, как, или вы можете рассмотреть возможность использования F#+, который имеет абстракцию Indexable для типы, которые обычно имеют Item имущество.

Конечно, вы можете игнорировать это предупреждение, с #nowarn "77" но, как вы уже видели, компилятор не может проверить, что кто-то вызовет вашу функцию с массивом и потерпит неудачу во время выполнения.

ОБНОВИТЬ

Поскольку вы задали следующий вопрос, как его использовать, вот пример:

#r "MathNet.Numerics.dll"
#r "MathNet.Numerics.FSharp.dll"
#r "FSharpPlus.dll"

open FSharpPlus
open MathNet.Numerics.LinearAlgebra

let x = item 1 [0..10]
let y = item 1 [|0..10|]
let z = item 1 (vector [0.;1.;2.])

// val x : int = 1
// val y : int = 1
// val z : float = 1.0

Я не уверен, что он будет работать с DiffSharp, и я не знаю, какой тип вы используете из этой библиотеки, я нашел DV многократно.

UPDATE2

Относительно вашего последующего вопроса для вашего общего transform Функция, использующая простое ограничение члена, будет недостаточной, вам также нужно будет в общем случае решить преобразование из списка в тип назначения, а также создать другое универсальное умножение, которое работает в общем, но с типами, с которыми вам нужно иметь дело. Вы можете использовать перегрузки в сочетании с ограничением члена, чтобы получить желаемую функциональность:

let inline item (i:int) (a: 'A) : 'a = (^A : (member get_Item: _ -> _) a, i)

type T = T with
    static member ($) (T, _:Vector<float>) = fun (x:float list) -> vector x
    static member ($) (T, _:Matrix<float>) = fun (x:float list) -> matrix [x]
    static member ($) (T, _:DV           ) = fun (x: D list  ) -> toDV (List.toArray x)

let inline toDestType (x:'t list) :'D = (T $ Unchecked.defaultof<'D>) x

type V = V with
    static member ($) (V, x:float        ) = fun (y: float) -> x * y : float
    static member ($) (V, x:D            ) = fun (y: float) -> x * y : D

let inline mult (y:float) (x:'t)  :'t = (V $ x) y

let inline transform (a:'T) :'T =
    let x1, y1 = mult l1 (cos (item 0 a)), mult l1 (sin (item 0 a))
    let x2, y2 = x1 + mult l2 (cos ((item 0 a) + (item 1 a))), y1 + mult l2 (sin ((item 0 a) + (item 1 a)))
    let g = toDestType [x1; y1; x2; y2]
    g 

let b = transform  (DV [| 1. ;  2.|])
let a = transform  (vector [1. ; 2.])

Я все еще получаю ошибку времени выполнения каждый раз, когда я ссылаюсь на DiffSharp, однако intellisense показывает правильные типы, выведенные.

Другие вопросы по тегам