Глобальная перегрузка операторов в F #
Я начинаю идти по пути определения собственных операторов для декартовых произведений и умножения матриц.
С матрицами и векторами, псевдонимами которых являются списки:
type Matrix = float list list
type Vector = float list
Я могу написать свой собственный код инициализации (и получить декартово произведение в сделке), написав
let inline (*) X Y =
X |> List.collect (fun x -> Y |> List.map (fun y -> (x, y)))
let createVector size f =
[0..size - 1] |> List.map (fun i -> f i)
let createMatrix rows columns f =
[0..rows - 1] * [0..columns - 1] |> List.map (fun i j -> f i j)
Пока все хорошо. Проблема в том, что мое определение *
устраняет нормальное определение, хотя моя версия определена только для списков, у которых нет собственного оператора умножения.
В документации http://msdn.microsoft.com/en-us/library/vstudio/dd233204.aspx указано, что "новые операторы имеют приоритет над встроенными операторами". Тем не менее, я не ожидал, что все числовые воплощения будут уничтожены - неужели статическая типизация позаботится об этом?
Я знаю, что можно определить глобальный оператор, который уважает типы, потому что MathNet Numerics использует *
для матричного умножения. Я также хотел бы спуститься по этой дороге, что потребовало бы, чтобы система набрала приоритет для умножения матрицы на декартово произведение типов Matrix
(которые сами являются списками).
Кто-нибудь знает, как это сделать в F #?
Ответы
Ответ 1
Вот (неидиоматическое) решение:
type Mult = Mult with
static member inline ($) (Mult, v1: 'a list) = fun (v2: 'b list) ->
v1 |> List.collect (fun x -> v2 |> List.map (fun y -> (x, y))) : list<'a * 'b>
static member inline ($) (Mult, v1:'a ) = fun (v2:'a) -> v1 * v2 :'a
let inline (*) v1 v2 = (Mult $ v1) v2
Ответ 2
Идиоматическое решение состоит в том, чтобы определить Matrix
как новый тип и добавить операторы в качестве статических членов:
type Matrix =
| MatrixData of float list list
static member (*) (MatrixData m1, MatrixData m2) = (...)
Операторы, определенные с помощью let
, полезны, когда вы знаете, что это определение не конфликтует ни с чем другим, или когда вы определяете оператор локально в небольшой области (внутри функции).
Для пользовательских типов рекомендуется определять операторы как статические члены. Другим преимуществом такого подхода является то, что ваш тип будет использоваться с С# (включая операторы).
Чтобы сделать это в вашем примере, мне пришлось изменить определение типа из псевдонима типа (который не является автономным типом и поэтому он не может иметь своих собственных статических членов) в однораздельный дискриминированный союз, который может иметь членов - но это, вероятно, хорошая идея.