Он говорит, что требуется /
дважды, потому что типы не нужны одинаково, поэтому ваша функция будет более общей, чем вы ожидаете.
При разработке общих математических библиотек необходимо принять некоторые решения. Вы не можете просто идти вперед без типа, аннотирующего все или ограничения математических операторов, вывод типа не сможет найти типы.
Проблема заключается в том, что операторы арифметики F # не ограничены, у них есть «открытая» подпись: 'a->'b->'c
, так что либо вы их ограничиваете, либо вы будете иметь полный тип, аннотируете все ваши функции, потому что если вы не будете F # чтобы знать, какая перегрузка должна произойти.
Если вы спросите меня, я возьму первый подход (который также является подходом Haskell), он немного сложный в начале, но как только вы создали все, достаточно быстро реализовать новые родовые типы и свои операции.
Объясняя это потребует еще один пост в этом блоге (я буду когда-нибудь), но я дам вам краткий пример:
// math operators restricted to 'a->'a->'a
let inline (+) (a:'a) (b:'a) :'a = a + b
let inline (-) (a:'a) (b:'a) :'a = a - b
let inline (*) (a:'a) (b:'a) :'a = a * b
let inline (/) (a:'a) (b:'a) :'a = a/b
type Vector2D<'a> = Vector2D of 'a * 'a
with
member this.X = let (Vector2D (x,_)) = this in x
member this.Y = let (Vector2D (_,y)) = this in y
static member inline (+) (Vector2D (lhsx, lhsy), Vector2D (rhsx, rhsy)) =
Vector2D(lhsx + rhsx, lhsy + rhsy)
static member inline (*) (Vector2D (x1, y1), Vector2D (x2, y2)) =
Vector2D (x1 * x2, y1 * y2)
static member inline (/) (Vector2D (x1, y1), Vector2D (x2, y2)) =
Vector2D (x1/x2, y1/y2)
static member inline get_One() :Vector2D<'N> =
let one:'N = LanguagePrimitives.GenericOne
Vector2D (one, one)
let inline doSomething1 (v : Vector2D<_>) = v * LanguagePrimitives.GenericOne
let inline doSomething2 (v : Vector2D<_>) = v/LanguagePrimitives.GenericOne
Так что идея не определить все перегрузки, связанные с типом с числовыми типами , вместо этого определите преобразования из числовых типов в ваш тип и только операции между вашим типом.
Теперь, если вы хотите, чтобы умножить свой вектор с другим вектором или целое число вы используете один и тот же перегрузку в обоих случаях:
let inline multByTwo (vector:Vector2D<_>) =
let one = LanguagePrimitives.GenericOne
let two = one + one
vector * two // picks up the same overload as for vector * vector
Конечно, теперь вы получаете в проблему создания родовых чисел, которые еще одна тема тесно связаны между собой. На SO есть много (разных) ответов о том, как сгенерировать модуль NumericLiteralG. F#+ - это библиотека, которая обеспечивает реализацию такого модуля, поэтому вы можете написать vector * 10G
Вы можете задаться вопросом, почему операторы F # не ограничены, как это происходит на других языках, таких как Haskell. Это связано с тем, что некоторые существующие типы .NET уже определены таким образом, простым примером является DateTime, которые поддерживают добавление целого числа, которое произвольно принято представлять в день, см. Больше обсуждений here.
UPDATE
Чтобы ответить на ваш РАЗВЕЙТЕ вопрос о том, как умножить число с плавающей точкой, снова это целая тема: Как создать общие номера с большим количеством возможных решений, в зависимости от того, как общей и масштабируемой вашей библиотеки ты хочешь быть. Во всяком случае здесь простой способ, чтобы получить некоторые из этих функциональных возможностей:
let inline convert (x:'T) :'U = ((^T or ^U): (static member op_Explicit : ^T -> ^U) x)
let inline fromNumber x = // you can add this function as a method of Vector2D
let d = convert x
Vector2D (d, d)
let result1 = Vector2D (2.0f , 4.5f) * fromNumber 1.5M
let result2 = Vector2D (2.0 , 4.5 ) * fromNumber 1.5M
F # + делает что-то подобное, но с рациональными числами вместо десятичной, чтобы сохранить больше точности.
Вы на что-то, и я думаю, что видел его раньше. Наблюдения: IntelliSense вошел в бесконечный цикл, экспериментируя с этим и даже раньше, не предсказал ошибку. Удаление первого определения '(*)' на векторе вызывает ту же ошибку с умножением. В 'doSomething' тип' l' недоопределен, однако, аннотируя его как тип компонента 'v', он не устраняет проблему. Я когда-то сталкивался с подобной путаницей при попытке использовать общие векторы для вывода матричной логики. Я сдался, потому что не мог понять, что делают IDE и компилятор. – Vandroiy
Небольшой комментарий - вы хотите поместить пробелы в '(*)', иначе они могут быть переданы в качестве начальных/конечных комментариев. –