F#,从鼠标光标位置相对于IInputElement提取网格坐标

时间:2016-05-05 22:34:17

标签: f#

如果我有任意大小的方格(在WPF / XAML中创建),每边有一定数量的单元格,那么通过获取鼠标光标位置来计算单击的单元格的坐标应该非常容易相对于网格元素。当我尝试学习F#时,我遇到了一些语法问题,并会欣赏一些输入。

这是我想要做的一个例子:

    // From a mouse event, fetch the position of the cursor relative to a IInputElement 
    let get_mouse_position (relative_to : IInputElement) (args : Input.MouseEventArgs) : (int*int) = 
        let position = args.GetPosition relative_to 
        ((Convert.ToInt32(position.X) / cellsPerSide), (Convert.ToInt32(position.Y) / cellsPerSide))

    // Get the position of the cursor relative to the grid 
    let get_cell_coordinates (element : IInputElement) =
        get_mouse_position element

但是,当我尝试使用通过在需要(x,y)坐标的其他地方调用get_cell_coordinates来检索的坐标时,我收到一条错误消息:

  

此表达式的类型为int * int,但此处的类型为' a - > int * int

那么,我做错了什么,为什么我得到这种多态类型而不仅仅是一个整数元组?

2 个答案:

答案 0 :(得分:2)

你得到的类型不是" polymorph"类型,它是一种功能类型。你得到类型为'a -> int * int的函数而不是你期望的int * int结果的原因是因为你没有将所有参数传递给你的函数,所以F#返回了一个预期其余函数的函数的参数。这被称为"部分应用程序",您可以在这里阅读更多相关信息:

https://fsharpforfunandprofit.com/posts/currying/

https://fsharpforfunandprofit.com/posts/partial-application/

这两篇文章的快速摘要:在F#中,所有函数都被视为接受一个参数并返回一个结果。是的,所有功能。当您创建一个看起来带有两个参数的函数时,F#会在内部重写它。它成为一个函数,它接受一个参数并返回第二个函数;第二个函数接受一个参数并返回结果。在这一点上,一个具体的例子可能是有用的。考虑这个功能:

let doubleAndSubtract a b = (a * 2) - b

(显然,a * 2周围的括号实际上并不需要,但我让它们进入以使函数明确无误地读取。)

在内部,F#实际将此函数重写为以下内容:

let doubleAndSubtract a =
    let subtract b = (a * 2) - b
    subtract

换句话说,它构建了一个"关闭" (捕获)您传入的a的值。因此以下两个函数完全等效:

let explicitSubtract b = (5 * 2) - b
let implicitSubtract = doubleAndSubtract 5

如果在F#interactive提示符中键入这些函数并查看它声明每个函数的类型,explicitSubtract的类型为b:int -> int,而implicitSubtract的类型为{{ 1}}。这两者之间的唯一区别是int -> int的类型命名参数,而explicitSubtract不知道"知道"其参数的名称。但两者都将采用int,并返回一个int(具体来说,10减去参数)。

现在,让我们看一下implicitSubtract的类型签名。如果您在F#交互式提示中键入它,您将看到其类型签名为doubleAndSubtract。在类型签名中,int -> int -> int是右关联的,因此签名实际上是-> - 换句话说,是一个接受int并返回一个接受int并返回int的函数的函数。 但是你也可以把它想象成一个带有两个整数并返回一个int的函数,你可以这样定义它。

所以,你的代码在这里发生的事情就是你定义了这样的函数:

int -> (int -> int)

这是一个带有两个参数(let get_mouse_position (relative_to : IInputElement) (args : Input.MouseEventArgs) : (int*int) = ... IInputElement类型)的函数,并返回一个int的元组... 但是相当于一个函数,它接受一个Input.MouseEventArgs类型的参数,并返回一个带IInputElement的函数并返回两个整数的元组。换句话说,您的功能签名是:

Input.MouseEventArgs

当您将其称为IInputElement -> Input.MouseEventArgs -> (int * int) 时,您只传递了一个参数,因此您获得的是get_mouse_position element类型的函数。类型检查器将此报告为类型Input.MouseEventArgs -> (int * int)(将'a -> int * int更改为通用类型名称Input.MouseEventArgs),原因是我不想进入此处(这已经很长) ,但这就是你获得结果的原因。

我希望这有助于您更好地理解F#功能。并阅读我链接的那两篇文章;他们会让你的理解更进一步。

答案 1 :(得分:0)

我通过使用静态Mouse.GetPosition方法来解决它,以获取鼠标的位置而不是Input.MouseEventArgs。

如果其他人有同样的问题,代码现在看起来如下:

    // From a mouse event, fetch the position of the cursor relative to a IInputElement 
let get_mouse_position (relative_to : IInputElement) : (int*int) = 
    let position = Mouse.GetPosition relative_to
    ((Convert.ToInt32(position.X) / cellsPerSide), (Convert.ToInt32(position.Y) / 32))

// Get the position of the cursor relative to the input element
let get_cell_coordinates (element : IInputElement) =
    get_mouse_position element