在 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
.
我有三种类型的位置 - 称为 Position、WalkPosition 和 TilePosition。我已将它们转换为度量单位,这样更简洁,但有些东西对我来说不太适用。
不幸的是,我没有使用纯 F#(有一个通过 CLI 公开的 C++ 接口 - 有趣的时光!)。首先,为了转换输入和输出,我使用了 * 1
[<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
.