2015-10-31 8 views
0

Это достаточно легко конвертировать единицы измерения к типу базового значения и обратно:Определение общего преобразования и из единиц измерения в F # для их основных типов

[<Measure>] 
type meter 

module Meter = 
    let from m = m * 1</meter> 
    let too m = m * 1<meter> 

Но потом я подумал, не так ли быть прекрасным, чтобы преобразовать из одного базового типа в блок измерения и обратно, в общем? Конечно, я могу на каждом конкретном случае быть конкретным, но я решил, что было бы удобно иметь оператора броска и литья, сохраняя при этом безопасность типов.

Моя первая попытка:

[<AutoOpen>] 
module Convert = 
    let inline (!->) x = x * 1<_> // (often) magically works 
    let inline (!-<) x = x * 1</_> // syntax error on the "/_" 

В !-> работает оператор в силу умозаключений типа. Было бы неплохо, если бы второй оператор тоже работал, но /_ не поддерживается синтаксисом. Я попробовал несколько типов аннотированных вариантов, но не смог найти прямое общее решение, отчасти потому, что мы не можем писать 'T<'M> или их вариант.

Я тогда думал использовать трюк, используемый для неявных преобразований, но применительно к явным преобразованиям, так как основной типа 'T<'M> если 'T и всегда существует явное приведение:

[<AutoOpen>] 
module Convert = 
    let inline explicit_convert (x:^a) : ^b = ((^a or ^b) : (static member op_Explicit : ^a -> ^b) x) 
    let inline (::>) x = explicit_convert x  // works both ways 

Это имеет дополнительное преимущество, что он работает на любом типе, который имеет явный оператор преобразования. К сожалению, компилятор жалуется, что не может быть безопасно сделать так:

ограничение членов с именем «» op_Explicit приведены особого статуса на F # компилятора, как некоторые .NET типы неявно дополненный с этим элементом. Это может привести к сбоям во время выполнения, если вы попытаетесь вызвать ограничение члена из вашего собственного кода.

Я не уверен, что я нахожусь на правильном пути или существует ли более безопасный тип создания оператора, который может перейти от любого измерения к базовому типу и обратно (включая сложные измерения, например int<meter^4> или int<meter/sec^2>)

ответ

3

LanguagePrimitives.FloatWithMeasure, Int32WithMeasure и т.д. методы предоставляются во время выполнения, что Wi в целом «унифицировать» значение.

Аналогичным образом, float, int и т. Д. Функции (обычно используемые для литья) будут удалять общий блок.

Сложный бит легко связывается с правильными функциями во время компиляции в общем виде. Ниже приводится то, что я думаю, вы хотите, следуя предложенному подходу here, который, я считаю, является стандартным трюком для такого рода вещей.

[<AutoOpen>] 
module Convert = 

    type AddUnits = AddUnits with 
     static member ($) (AddUnits, x: float) = LanguagePrimitives.FloatWithMeasure<_>(x) 
     static member ($) (AddUnits, x: int) = LanguagePrimitives.Int32WithMeasure<_>(x) 

    let inline (!->) x = AddUnits $ x 

    type StripUnits = StripUnits with 
     static member ($) (StripUnits, x:float<_>) = float x 
     static member ($) (StripUnits, x:int<_>) = int x 

    let inline (!-<) x = StripUnits $ x 

open FSharp.Data.UnitSystems.SI.UnitSymbols 

!-< 22<m>  // 22 
!-< 9.8<m/s^2> // 9.8 

let x : int<m> = !-> 22   // 22<m> 
let y : float<m/s^2> = !-> 9.8 // 9.8<m/s^2> 
+0

Спасибо, это выглядит многообещающе. Я не знаком с синтаксисом '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '? Это что-то автоматическое отображение Du? – Abel

+0

@Abel $ - это просто выбор латынь, сделанный для его пользовательского оператора (он мог бы выбрать (+), если бы захотел).Он сделал такой выбор, чтобы подражать «приложению» оператора в Haskell (см. [Здесь] (http://stackoverflow.com/a/19521376/4925216)) – Sehnsucht

+2

Первая часть была уже в [предыдущем ответе] (http: /stackoverflow.com/questions/28786971/generic-units-in-f/28787251#28787251). Тот факт, что вы предложили точно такой же код, заставляет меня думать, что он определенно должен быть частью библиотеки F #;) – Gustavo

0

Вы на самом деле уже написали функцию для удаления единиц измерения - см этого примера:

> let t:int = !->1<m>;; 

val t : int = 1 
+0

Он работал в одном направлении, да, но не в обоих – Abel

+0

Это делает работу в обоих - вам просто нужно правильные аннотации типов –