Сопоставление с образцом единиц измерения в 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>