Ограничения типа F # для векторных операций - PullRequest
4 голосов
/ 29 октября 2019

Я хотел бы написать несколько общих функций и типов в F # для работы с векторами. У меня есть несколько различных типов данных со статическими операторами (+) и (*), поэтому я могу добавить их и умножить на скаляры (на данный момент float s).

Например, я успешно получилКласс Vec2, где я могу написать

let v = 3.0 * Vec2(1.,1.) + Vec2(3.,4.)

Допустим, у меня также есть Vec3 или любой другой тип вектора. Вот два примера (псевдокод) того, что я хотел бы написать:

Обобщенная функция для векторов

Я думаю, что это возможно при статически разрешенных ограничениях типов, которые утверждают, что 'V имеет(+) и (*) но я не могу заставить его работать. Было бы неплохо, если бы я мог назвать свое ограничение типа следующим образом.

let average<'V when 'V : vector> (v1:'V) (v2:'V) =
    0.5 * (v1 + v2)

Есть ли альтернатива, которая действительно работает?

Универсальный тип, который сам по себе является векторным типом

Для любого типа 'T и типа 'V, представляющего вектор, мы можем добавлять и скалярно умножать функции 'T -> 'V как векторы. Я хочу создать тип, подобный

type VecFunc<'T,'V when 'V : vector> = ...

. В качестве простого примера, f : VecFunc<int,Vec2> может хранить функцию, принимающую int x и возвращающую Vec2 с обоими компонентами, равными float x,Может быть, мы могли бы оценить основную функцию, вызвав метод Eval:

f.Eval(3) // would return Vec2(3.,3.)

Я бы хотел трактовать VecFunc<int,Vec2> как векторный тип, давая ему операции (+) и (*), чтобы яможно вычислить

(-2.0 * f + f).Eval(2) // returns Vec2(-4., -4.)

или объединить его с первым примером:

(average f g).Eval(1) // ...

Можно ли использовать интерфейсы F # или параметры типа для достижения этих результатов?

1 Ответ

3 голосов
/ 30 октября 2019

Вы можете сделать что-то в том же духе, потребовав, чтобы ваши векторные типы (а также функции на векторах) реализовали определенные операторы. Исходя из вашего примера, я полагаю, у вас уже есть + для Vec2 и * для умножения вектора на скаляр. Вы можете написать функцию average в терминах этих операторов, и тогда она будет работать с любым типом, у которого есть эти операторы.

Единственная проблема заключается в том, что F # обрабатывает * в некотором особом порядке, и поэтому выне может легко сделать это, если у вас есть * типа float * vector -> vector. Кажется, это работает нормально, если вы используете .* для скалярного умножения на вектор (и аналогично, вы можете добавить *. для другого направления).

Вот мои определения Vec2 и Vec3 с этими операторами:

type Vec2(a:float, b:float) = 
  member x.A = a
  member x.B = b
  static member (.*) (a:float, v:Vec2) = 
    Vec2(a*v.A, a*v.B)
  static member (+) (v1:Vec2, v2:Vec2) = 
    Vec2(v1.A+v2.A, v1.B+v2.B)

type Vec3(a:float, b:float, c:float) = 
  member x.A = a
  member x.B = b
  member x.C = c
  static member (.*) (a:float, v:Vec3) = 
    Vec3(a*v.A, a*v.B, a*v.C)
  static member (+) (v1:Vec3, v2:Vec3) = 
    Vec3(v1.A+v2.A, v1.B+v2.B, v1.C+v2.C)

Теперь вы можете написать average как встроенную функцию, которая использует статические ограничения членов:

let inline average (v1:^V) (v2:^V) =
  (^V : (static member (.*) : float * ^V -> ^V) (0.5, v1 + v2))

average (Vec2(1.,1.)) (Vec2(3.,4.))
average (Vec3(1.,1.,1.)) (Vec3(3.,4.,5.))

F # автоматически добавляет ограничение, если вы используете + оператор, поэтому я могу просто написать v1 + v2. Оператор .* является нестандартным, и поэтому мне пришлось вызывать его явно.

Для второй части вашего вопроса - как вы заметили, типы F # не могут параметризоваться другими типами с ограничениями статического типа, поэтомудля этого требуется еще несколько хитростей. Один из вариантов, который у вас есть, - это добавить нужные операции в качестве параметров типа, а затем иметь функцию inline, которая захватывает операции и передает их как обычные функции вашему типу VecFunc. Вот пример:

type VecFunc<'T1, 'T2>(f:'T1 -> 'T2, mult:float * 'T1 -> 'T1, add:'T2 * 'T2 -> 'T2) = 
  member x.F = f
  member x.Mult = mult
  member x.Add = add
  static member (.*) (a:float, f:VecFunc<_, _>) = 
    VecFunc((fun v -> f.F (f.Mult(a, v))), f.Mult, f.Add)
  static member (+) (f1:VecFunc<_, _>, f2:VecFunc<_, _>) = 
    VecFunc((fun v -> f1.Add(f1.F v, f2.F v)), f1.Mult, f1.Add)

let inline vfunc (f:^V -> ^T) = 
    VecFunc< ^V, ^T>(f, 
      (fun (a, b) -> (^V : (static member (.*) : float * ^V -> ^V) (a, b))),
      (fun (a, b) -> a + b))

let vf = vfunc (fun (v:Vec2) -> v + v)
average vf vf 

Эта проверка типов, но я не совсем уверен, правильно ли она работает (я не уверен, что должны делать сложение и умножение векторных функций!) -но в любом случае это может помочь вам найти правильное направление.

...