Единицы измерения, интерфейсы и миксины - PullRequest
3 голосов
/ 03 августа 2011

Рассмотрим следующий код F #:

type ILinear =
  interface 
  end

type IMetric =
  interface
  end


[<Measure>] type cm =
    interface ILinear
    interface IMetric 

[<Measure>] type m =
    interface ILinear
    interface IMetric

[<Measure>] type time

Я хочу использовать эти интерфейсы как для группировки типов мер, так и для обеспечения общего уровня между «любым показателем» и «определенным измерением» - что-то похожее на:

(* Yes, I know this syntax is probably incorrect *)    
let rate (distance:float<'u:#ILinear,#IMetric>) (time:float<time>) =
       distance/time

Я понимаю, что это, вероятно, раздвигает границы возможностей, но мне просто любопытно, возможно ли это, и если да, то какой будет синтаксис. Как я уже сказал, это использование интерфейсов как своего рода миксин бедняка.

1 Ответ

5 голосов
/ 03 августа 2011

Не думаю, что это возможно, но мне очень нравится идея :-).

Если бы это было возможно, то ограничения, вероятно, были бы записаны с использованием того же синтаксиса, который можно использовать для записи ограничений интерфейса для параметров обычного (не измеряемого) типа:

let rate<[<Measure>] 'u when 'u :> IMetric> (distance:float<'u>) (time:float<time>) =
    distance/time

в сообщении об ошибке ясно сказано, что ограничения могут быть указаны только для параметров обычного типа (на самом деле, я даже удивился, что единицы измерения могут реализовывать интерфейсы - это не выглядит очень полезным, поскольку они полностью стираются во время компиляции):

ошибка FS0703: ожидаемый параметр типа, а не параметр единицы измерения

Лучший обходной путь, о котором я могу подумать, - это написать простую оболочку, которая хранит значение (с некоторой единицей измерения)) и дополнительный (фантомный) тип, представляющий ограничения:

[<Struct>]    
type FloatValue<[<Measure>] 'u, 'constr>(value:float<'u>) =
  member x.Value = value

let cm f = FloatValue<_, IMetric>(f * 1.0<cm>)

Функция cm принимает значение с плавающей точкой и упаковывает его в FloatValue.Второй тип аргумента является аргументом обычного типа, поэтому он может быть предоставлен с некоторым типом, который реализует интерфейсы (или только с одним интерфейсом).Тогда функция rate выглядит следующим образом:

let rate (distance:FloatValue<'u, #IMetric>) (time:float<time>) =
  distance.Value / time

Поскольку ограничения не могут быть указаны для типа объекта, мы должны указать их для аргумента второго типа.Затем вы можете вызвать функцию, используя:

rate (cm 10.0) 5.0<time>
...