Сопоставление с образцом единиц измерения в F#

Эта функция:

let convert (v: float<_>) =
  match v with
  | :? float<m> -> v / 0.1<m>
  | :? float<m/s> -> v / 0.2<m/s>
  | _ -> failwith "unknown"

выдает ошибку

Тип 'float<' u>'не имеет надлежащих подтипов и не может использоваться в качестве источника для проверки типа или приведения во время выполнения.

Есть ли способ, как сопоставить единицы измерения соответствия?

3 ответа

Решение

Как подробно объясняет @kvb, проблема в том, что единицы измерения являются частью типа. Это означает, что float<m> отличается от типа float<m/s> (и, к сожалению, эта информация не сохраняется как часть значения во время выполнения).

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

type SomeValue = 
  | M of float<m>
  | MPS of float<m/s>

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

let convert v = 
  match v with 
  | M v -> v / 0.1<m>
  | MPS v -> v / 0.2<m/s>

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

Для нормальных типов, таких как int а также float Вы также можете использовать перегруженные члены (объявленные в некотором типе F#), но это не работает для единиц измерения, потому что подпись будет такой же после того, как компилятор F# сотрет информацию о единицах.

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

let convert (v: float<'u>) = //'
  match v with
  | :? float<m> -> v / 0.1<m>
  | :? float<m/s> -> v / 0.2<m/s>
  | _ -> failwith "unknown"

Сообщение об ошибке говорит вам, что компилятор знает, что v имеет тип float<'u>, а также float<'u> не имеет надлежащих подтипов, поэтому нет смысла делать тест типа, чтобы определить, является ли это float<m> или любой другой тип.

Вы можете попытаться обойти это путем первого бокса v в объект, а затем делать тест типа. Это будет работать, например, если у вас есть list<'a> и хотел посмотреть, было ли это list<int>потому что полная информация о типах об универсальных объектах отслеживается во время выполнения, включая параметры универсального типа (в частности, это отличается от того, как работают некоторые другие среды выполнения, например, Java). К сожалению, единицы измерения F# стираются во время выполнения, поэтому здесь это не сработает - система не сможет определить правильный тип меры при представлении в штучной упаковке, поскольку во время выполнения значение является простым float Система F# для единиц измерения на самом деле очень похожа на то, как Java обрабатывает универсальные типы.

Кроме того, то, что вы пытаетесь сделать, кажется довольно подозрительным - функции, которые являются общими в единице измерения, не должны делать разные вещи в зависимости от типа меры; они должны быть правильно параметрическими. Что именно вы пытаетесь достичь? Это, конечно, не похоже на операцию, которая соответствует физической реальности, которая является основой для типов измерений F#.

См. Раздел " Единицы во время выполнения" по адресу http://msdn.microsoft.com/en-us/library/dd233243.aspx.

Я согласен с @kvb, я думаю, что лучший способ обойти это - передать объект.

Что я хотел бы сделать, используя вашу структуру кода:

let convert (v: float<_>) =
  match v with
  | :? float<m> -> v<m>
  | :? float<inches> -> v * 2.54 / 100.0<m>
Другие вопросы по тегам