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

Bit Shifting with units of measure in F#

我有三种类型的位置 - 称为 Position、WalkPosition 和 TilePosition。我已将它们转换为度量单位,这样更简洁,但有些东西对我来说不太适用。

不幸的是,我没有使用纯 F#(有一个通过 CLI 公开的 C++ 接口 - 有趣的时光!)。首先,为了转换输入和输出,我使用了 * 1 和 * 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>)

我愿意撕掉度量单位(|> int 在这种情况下似乎不会减慢它的速度)并将其添加回返回的距离,但我似乎做不到那。我什至不能超载内联调用,因为你不能纯粹根据度量单位超载。想法?

您看到的错误是因为 |~| 函数中的度量单位被限制为 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>

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

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