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

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

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», поскольку параметр типа не может быть разрешен во время компиляции.


person François-David Collin    schedule 27.03.2017    source источник


Ответы (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#+, который имеет Индексируемая абстракция для типов, которые обычно имеют свойство 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 несколько раз.

ОБНОВЛЕНИЕ 2

Что касается вашего дополнительного вопроса для вашей общей функции 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 показывает правильные типы.

person Gus    schedule 27.03.2017
comment
У меня это работает для Vector<float> Mathnet.Numerics, но не для DV DiffSharp, чей get_Item возвращает тип D, а не float. - person François-David Collin; 27.03.2017
comment
Но что он возвращает в DV DiffSharp, когда вы используете оператор .[]? - person Gus; 27.03.2017
comment
Значение типа D - person François-David Collin; 27.03.2017
comment
Итак, если он возвращает D при обычном вызове, почему он должен возвращать что-то другое при вызове со статическими ограничениями? - person Gus; 27.03.2017
comment
Это не так, просто я не могу создать общую функцию transform, которая учитывает не плавание для 'a, см. мое редактирование выше. - person François-David Collin; 27.03.2017
comment
Хорошо, это другая проблема. Это потому, что вы ограничиваете его своим кодом. Вы можете попробовать удалить аннотации типа 'a, изменить его на _ и посмотреть, выводит ли он его. В противном случае я могу попробовать это позже, но мне придется загрузить эти библиотеки. - person Gus; 27.03.2017
comment
точно, это другая проблема. Не могли бы вы привести краткий пример того, как следует использовать абстракцию Indexable? - person François-David Collin; 27.03.2017
comment
Я добавил пример, используя Indexable. Что касается вашего кода, мне не удалось его воспроизвести, есть много проблем, например, l1 не определено. Мой совет: когда публикуете код, делайте его как можно более полным (как я), включая директивы #r, открытые пространства имен, все объявления. Чем полнее, тем больше шансов, что кто-то сможет вам помочь. - person Gus; 28.03.2017
comment
Извините за это, я добавил несколько строк преамбулы. Просто установите DiffSharp и MathNet.Numerics вместе с paket и generate-load-scripts, чтобы получить скрипты пакетов. - person François-David Collin; 28.03.2017
comment
Хорошо, позже попробую еще раз. Какую версию F# вы используете? - person Gus; 28.03.2017
comment
4.1 с последней версии по сравнению с 2017, я применяю структуру net46 для MathNet, в противном случае она придерживается net.core - person François-David Collin; 28.03.2017
comment
Я не могу заставить DiffSharp работать. Я получаю это во время выполнения: «Была предпринята попытка загрузить программу с неправильным форматом». Но intellisense указывает вам, в чем проблема: let x1, y1 = l1 * cos ( ... в этом умножении он говорит вам, что переменная типа 'a ограничена значением float. Это потому, что вы умножаете на l1 (число с плавающей запятой). Итак, для этого конкретного сценария вам понадобится механизм для преобразования float в D общим способом, который явно выходит за рамки вашего вопроса, и я не знаю DiffSharp. Однако я буду рад помочь, если вы укажете мне такую ​​функцию. - person Gus; 29.03.2017
comment
Diffsharp использует внутреннее представление числа (например, с плавающей запятой), которое похоже на комплексные числа, но с другой арифметикой, оно называется двойным числом и просто набирается с помощью D. Таким образом, вместо вектора это DV, элементы которого являются числами типа D. Чтобы преобразовать число с плавающей запятой, например l1, в тип D, это просто D l1. И вы правы, если мы преобразуем вот так l1 и l2, компилятор теперь счастлив. Но в пакете DiffSharp есть (*) float * D -> D перегруженный оператор, я не понимаю, почему механизм вывода типов его не находит (баг?). - person François-David Collin; 29.03.2017
comment
Кстати, что эквивалентно элементу get_Item для среза (диапазона)? - person François-David Collin; 29.03.2017
comment
См. update2, он становится действительно хакерским из-за умножения и обратного преобразования в целевой тип. - person Gus; 02.04.2017
comment
Ах, это действительно приятно, и я подтверждаю, что это работает. У вас есть ошибка времени выполнения из-за собственных библиотек, необходимых DIffSharp в вашем __SOURCE_DIRECTORY__ (они находятся в каталоге build пакета). Тем временем я получил рабочую версию функции преобразования с явной параметризацией функции преобразования базового типа (чтобы устранить ошибки вывода для типа подписи (*)) и отказом от типа Vector<'T> в пользу float[] из-за слишком большого количества несовместимых жестко закодированных ограничения типа для базового типа 'T (я думаю, смягчены вашим Unchecked.defaultof). - person François-David Collin; 03.04.2017
comment
Спасибо, мне было не очевидно, что отсутствует родная .dll. Что касается вашего решения, хотя у меня нет перед собой кода, из вашего описания это выглядит как путь. Решение, которое я вам показал, на 100 % универсальное, но в то же время на 100 % хакерское. - person Gus; 03.04.2017
comment
Кстати, вы знаете, почему механизм вывода типов не может выводить инфиксные операторы разнородных типов, такие как float * D -> D ? Это по дизайну? - person François-David Collin; 03.04.2017
comment
Можно, попробуйте изменить мою функцию mult на инфиксный оператор. Это точно сработает. Проблема заключается в определении F# универсального оператора *. - person Gus; 03.04.2017