【问题标题】:Bit Shifting with units of measure in F#在 F# 中使用测量单位进行位移
【发布时间】:2016-05-17 00:39:49
【问题描述】:

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

不幸的是,我没有使用纯粹的 F#(有一个通过 CLI 公开的 C++ 接口 - 很有趣!)。首先,为了转换输入和输出,我使用了 * 1 和 * 1,因为我注意到使用 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 在这种情况下似乎不会减慢它)并将其添加回返回的距离,但似乎我不能这样做。我什至不能重载内联调用,因为你不能纯粹在度量单位上重载。想法?

【问题讨论】:

  • 你说你不能超载计量单位。在某些情况下它可以工作,你想重载什么?你能展示一个(不工作的)示例代码吗?
  • 我不清楚到底是什么问题。上面的代码可以编译,所以我认为它是别的东西?
  • 在底部添加了一个失败的例子,并说明了类型签名不正确的原因。

标签: f# units-of-measurement


【解决方案1】:

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

int 函数将从 int&lt;'u&gt; 类型的东西中删除度量单位,这是正确的。您可能不知道的是,您可以使用LanguagePrimitives.Int32WithMeasure&lt;'u&gt; 添加特定的计量单位。

因此你可以写|~|:

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&lt;walk&gt;

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

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

【讨论】:

  • 我已经对内联和避免 int 强制转换以删除度量单位进行了一些性能改进。对于第一种情况,您的链接以一种不完全明显的方式显示“内联函数有助于优化代码”。对于第二种情况,已明确记录这应该没有效果。我没有一个明确的例子可以展示。
猜你喜欢
  • 2013-03-12
  • 2010-09-07
  • 2014-09-22
  • 1970-01-01
  • 2012-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-28
相关资源
最近更新 更多