2015-03-01 1 views
3

Я нашел хорошую статью: http://nut-cracker.azurewebsites.net/blog/2011/08/09/operator-overloading/Общие операции artihmetical на родовых типов в F #

и решил немного поиграть с F # с использованием информации, представленной в статье. я создал свой собственный тип Vector2D < «а>, поддерживая сложение и умножение на константу:

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 (*) (factor, Vector2D (x, y)) = 
      Vector2D (factor * x, factor * y) 
     static member inline (*) (Vector2D (x, y), factor) = 
      Vector2D (factor * x, factor * y) 

Это работает отлично - я не наблюдал каких-либо проблем при использовании этих элементов. Тогда я решил добавить поддержку для деления на константу, а также (с намерены реализовать, например, нормализуя векторы):

static member inline (/) (Vector2D (x, y), factor) = 
     Vector2D (x/factor, y/factor) 

Дело в том, даже если он компилируется нормально, каждый раз, когда я пытаюсь использовать его, я вижу ошибка:

let inline doSomething (v : Vector2D<_>) = 
    let l = LanguagePrimitives.GenericOne 
    v/l // error 

сообщение об ошибке:

параметр типа отсутствует ограничение «, когда (^ а или^18259?): (статический член (/):^а *^18259 -> ^? 18260) '

Более того - infered типа в DoSomething является: Vector2D < 'а> -> Vector2D <' с> (требуется член (/) и член (/) и член get_One)

Мой вопрос: почему это возможно используйте члены (*) и (+), но это невозможно сделать с (/), хотя они определены одинаково? Кроме того, почему признанный тип говорит, что ему нужно (/) член дважды?

+1

Вы на что-то, и я думаю, что видел его раньше. Наблюдения: IntelliSense вошел в бесконечный цикл, экспериментируя с этим и даже раньше, не предсказал ошибку. Удаление первого определения '(*)' на векторе вызывает ту же ошибку с умножением. В 'doSomething' тип' l' недоопределен, однако, аннотируя его как тип компонента 'v', он не устраняет проблему. Я когда-то сталкивался с подобной путаницей при попытке использовать общие векторы для вывода матричной логики. Я сдался, потому что не мог понять, что делают IDE и компилятор. – Vandroiy

+1

Небольшой комментарий - вы хотите поместить пробелы в '(*)', иначе они могут быть переданы в качестве начальных/конечных комментариев. –

ответ

3

Он говорит, что требуется / дважды, потому что типы не нужны одинаково, поэтому ваша функция будет более общей, чем вы ожидаете.

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

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

+0

Ничего себе - это выглядит впечатляюще! Теперь я вижу, что случилось с моей ошибкой - я просто не знал, что функции слишком общие. Спасибо, что поняли это. Я понимаю, как работают операции (умножения и т. Д.). Не могли бы вы просто объяснить мне, как реально реализовать умножение на число с плавающей запятой (например, 1.5)? Есть ли лучший способ написать это как дробь, умножить на числитель и делить на знаменатель? –

+1

@AlojzyLeszcz Это большая тема, но ознакомьтесь с обновлением для простого способа. – Gustavo

+0

Отлично - теперь я чувствую себя намного мудрее! : D. Большое спасибо! –