5

データ型と測定単位の両方で汎用的な関数を定義することは可能ですか? たとえば、私がやりたいことはコンパイルされません(測定単位が存在しなくてもコンパイルされませんが、私がやりたいことを伝えていると思います):

let inline dropUnit (x : 'a<_>) = x :> typeof(a)

ここでの考え方は、"kg" や "l" などのいくつかの測定単位と識別結合を定義したということです。

type Unit = 
  | Weight of float< kg >
  | Volume of float < l >

そして、私は次のようなことをしたいと思います:

let isValidUnitValue myUnit =
   match myUnit with
       | Weight(x) -> (dropUnit x) > 0.
       | Volume(x) -> (dropUnit x) > 0.

この特定のケースでは、単に使用できることを認識しています

let dropUnit (x : float<_>) = (float) x

しかし、上記を書いているときに、一般的なケースについて疑問に思い始めました。

4

1 に答える 1

11

isValidUnitValue関数の書き方に関する具体的な質問に対する答えは次のとおりです。

let inline isValidUnitValue myUnit = myUnit > LanguagePrimitives.GenericZero

したがって、DiscriminatedUnionを定義する必要はありません。

dropUnit元の質問に関しては、データ型に対して一般的であり、短い答えのように測定単位の両方である関数を定義することが可能かどうかはノーです。そのような関数が存在する場合、それはのような署名を持ち、 'a<'b> -> 'aそれを表すために型システムはより高い種類を実装する必要があります。

ただし、オーバーロードとインラインを使用するトリックがあります。

  1. オーバーロードの使用(C#)
    type UnitDropper = 
        static member drop (x:sbyte<_>  ) = sbyte   x
        static member drop (x:int16<_>  ) = int16   x
        static member drop (x:int<_>    ) = int     x
        static member drop (x:int64<_>  ) = int64   x
        static member drop (x:decimal<_>) = decimal x
        static member drop (x:float32<_>) = float32 x
        static member drop (x:float<_>  ) = float   x

    [<Measure>] type m
    let x = UnitDropper.drop 2<m> + 3

しかし、これは実際にはジェネリック関数ではなく、その上にジェネリックなものを書くことはできません。

> let inline dropUnitAndAdd3 x = UnitDropper.drop x + 3 ;;
-> error FS0041: A unique overload for method 'drop' could not be determined ...

  1. インラインを使用する一般的なトリックは、再入力です。
    let inline retype (x:'a) : 'b = (# "" x : 'b #)

    [<Measure>] type m
    let x = retype 2<m> + 3
    let inline dropUnitAndAdd3 x = retype x + 3

問題はそれretypeがあまりにも一般的であるということです、それはあなたが書くことを可能にするでしょう:

let y = retype 2.0<m> + 3

これはコンパイルされますが、実行時に失敗します。


  1. オーバーロードとインラインの両方を使用する:このトリックは、中間型を介したオーバーロードを使用することで両方の問題を解決します。これにより、コンパイル時のチェックが両方とも取得され、ジェネリック関数を定義できるようになります。
    type DropUnit = DropUnit with
        static member ($) (DropUnit, x:sbyte<_>  ) = sbyte   x
        static member ($) (DropUnit, x:int16<_>  ) = int16   x
        static member ($) (DropUnit, x:int<_>    ) = int     x
        static member ($) (DropUnit, x:int64<_>  ) = int64   x
        static member ($) (DropUnit, x:decimal<_>) = decimal x
        static member ($) (DropUnit, x:float32<_>) = float32 x
        static member ($) (DropUnit, x:float<_>  ) = float   x

    let inline dropUnit x = DropUnit $ x

    [<Measure>] type m
    let x = dropUnit 2<m>   + 3
    let inline dropUnitAndAdd3 x = dropUnit x + 3
    let y = dropUnit 2.0<m> + 3   //fails at compile-time

最後の行で、コンパイル時エラーが発生します。FS0001: The type 'int' does not match the type 'float'

このアプローチのもう1つの利点は、次のように型定義で静的メンバー($)を定義することにより、後で新しい型で拡張できることです。

    type MyNumericType<[<Measure 'U>]> =
        ...
        static member dropUoM (x:MyNumericType<_>) : MyNumericType = ...
        static member ($) (DropUnit, x:MyNumericType<_>) = MyNumericType.dropUoM(x)
  1. いくつかの一般的な制約を利用する:
    let inline retype (x: 'T) : 'U = (# "" x: 'U #)
    let inline stripUoM (x: '``Num<'M>``) =
        let _ = x * (LanguagePrimitives.GenericOne : 'Num)
        retype x :'Num

これは2)に似ていますが、型注釈は必要ありません。制限は、数値タイプでのみ機能することですが、通常はUoMのユースケースです。

于 2013-01-05T08:58:17.203 に答える