使用F#中的度量单位进行位移

时间:2016-02-07 08:11:55

标签: f# units-of-measurement

我有三种类型的位置 - 名为Position,WalkPosition和TilePosition。我已将它们转换为测量单位,并且它更清晰,但有些事情对我来说并不适用。

不幸的是,我没有使用纯粹的F#(通过CLI公开了一个C ++界面 - 有趣的时候!)。首先,对于转换和转出,我使用了* 1< tile>和* 1< 1 / tile>因为我注意到我使用int的性能受到了影响。在我开始尝试使用泛型做有趣的事情之前,这一切都很好。我目前被getApproxDistance函数困扰,我正在调用一个运算符|〜|。假设此版本没有附加到我的职位的计量单位:

[<Measure>] type pixel
[<Measure>] type walk
[<Measure>] type tile

module Position =
    type Position<[<Measure>] 'u> = Pos of int<'u> * int<'u> with
        static member inline (+) (Pos (x1, y1), Pos (x2, y2)) = Pos (x1 + x2, y1 + y2)
        static member inline (-) (Pos (x1, y1), Pos (x2, y2)) = Pos (x1 - x2, y1 - y2)
        static member inline (*) (Pos (x, y), f) = Pos (x * f, y * f)
        static member inline (/) (Pos (x, y), d) = Pos (x / d, y / d)
        // getApproxDistance as per Starcraft: Broodwar
        static member (|~|) (Pos (x1, y1), Pos (x2, y2)) =
            let xDist = abs (x1 - x2)
            let yDist = abs (y1 - y2)
            let largeDist, smallDist = max xDist yDist, min xDist yDist
            if smallDist < (largeDist >>> 2) then largeDist
            else
                let smallCalc = (3*smallDist) >>> 3
                ((smallCalc >>> 5) + smallCalc + largeDist - (largeDist >>> 4) - (largeDist >>> 6))
        // Precise length calc - may be slow
        static member inline (|-|) (Pos (x1, y1), Pos (x2, y2)) =
            pown (x1 - x2) 2 + pown (y1 - y2) 2 |> float |> sqrt

    let inline posx (Pos (_x, _)) = _x
    let inline posy (Pos (_, _y)) = _y

    let PixelPerWalk : int<pixel/walk> = 8<pixel/walk>
    let PixelPerTile : int<pixel/tile> = 32<pixel/tile>
    let WalkPerTile : int<walk/tile> = 4<walk/tile>

    let inline walkToPixel (pos:Position<_>) = pos * PixelPerWalk
    let inline tileToPixel (pos:Position<_>) = pos * PixelPerTile

    let inline pixelToWalk (pos:Position<_>) = pos / PixelPerWalk
    let inline tileToWalk (pos:Position<_>) = pos * WalkPerTile

    let inline pixelToTile (pos:Position<_>) = pos / PixelPerTile
    let inline walkToTile (pos:Position<_>) = pos / WalkPerTile

    let example = Pos (1<walk>, 2<walk>) |~| Pos (2<walk>, 1<walk>)

我会满足于关闭度量单位(|&gt; int在这种情况下似乎没有减慢速度)并将其添加回返回的距离,但似乎我做不到。我甚至无法重载内联调用,因为您不能仅仅在度量单位上过载。想法?

1 个答案:

答案 0 :(得分:4)

您看到的错误是因为|~|函数中的度量单位被限制为1。这意味着int<walk>不是此函数的有效输入。

您是正确的int函数将从int<'u>类型的某些内容中删除度量单位。您可能不知道的是,您可以使用LanguagePrimitives.Int32WithMeasure<'u>添加特定的度量单位。

因此,您可以撰写|~|

static member (|~|) (Pos (x1 : int<'u>, y1 : int<'u>), Pos (x2 : int<'u>, y2 : int<'u>)) =
    let xDist = abs (int x1 - int x2)
    let yDist = abs (int y1 - int y2)
    let largeDist, smallDist = max xDist yDist, min xDist yDist
    if smallDist < (largeDist >>> 2) then 
        largeDist 
        |> LanguagePrimitives.Int32WithMeasure<'u> 
    else
        let smallCalc = (3*smallDist) >>> 3
        ((smallCalc >>> 5) + smallCalc + largeDist - (largeDist >>> 4) - (largeDist >>> 6))
        |> LanguagePrimitives.Int32WithMeasure<'u>

你的例子:

let example = Pos (1<walk>, 2<walk>) |~| Pos (2<walk>, 1<walk>)

然后具有正确的类型:int<walk>

您不应该担心剥离/添加度量单位对性能的影响,它们只是一个编译时构造 - 度量信息不会在运行时保留。

顺便说一句,您并不是真的需要所有这些inline个关键字,您没有使用静态解析的类型参数做任何事情,有关更多详细信息,请参阅https://msdn.microsoft.com/en-us/library/dd548047.aspx关于使用inline