Reputation: 349
If I have a square grid (created in WPF/XAML) in any size, with a given number of cells per side, it should be ridiculously easy to calculate the coordinates to a cell that was clicked by getting the mouse cursor position relative to the grid element. As I try to learn F#, I experience some problems with the syntax and would appreciate some input.
Here is an example of what I am trying to do:
// 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
However, when I try to use the coordinates retrieved by calling get_cell_coordinates somewhere else where (x,y) coordinates are needed, I get an error that says:
This expression was expected to have type int * int but here has type 'a -> int * int
So, what am I doing wrong and why do I get this polymorph type and not just a tuple of integers?
Upvotes: 0
Views: 321
Reputation: 36688
The type you got is not a "polymorph" type, it's a function type. The reason you got a function of type 'a -> int * int
instead of the int * int
result you were expecting is because you didn't pass all the parameters to your function, so F# returned a function that expected the rest of the parameters. This is called "partial application", and you can read more about it here:
https://fsharpforfunandprofit.com/posts/currying/
and
https://fsharpforfunandprofit.com/posts/partial-application/
A quick summary of the two articles: in F#, all functions are treated as taking one parameter and returning one result. Yes, ALL functions. When you create a function that appears to take two parameters, F# rewrites it internally. It becomes a function that takes one parameter and returns a second function; this second function takes one parameter and returns the result. A concrete example will probably be useful at this point. Consider this function:
let doubleAndSubtract a b = (a * 2) - b
(Obviously, the parentheses around a * 2
aren't actually needed, but I left them in to make the function unambiguous to read).
Internally, F# actually rewrites this function into the following:
let doubleAndSubtract a =
let subtract b = (a * 2) - b
subtract
In other words, it builds a function that "closes over" (captures) the value of a
that you passed in. So the following two functions are completely equivalent:
let explicitSubtract b = (5 * 2) - b
let implicitSubtract = doubleAndSubtract 5
If you type these functions in to the F# interactive prompt and look at the types that it declares each function to have, explicitSubtract
has type b:int -> int
, and implicitSubtract
has type int -> int
. The only difference between these two is that the type of explicitSubtract
names the parameter, whereas implicitSubtract
doesn't "know" the name of its parameter. But both will take an int, and return an int (specifically, 10 minus the parameter).
Now, let's look at the type signature for doubleAndSubtract
. If you typed it into the F# interactive prompt, you'll have seen that its type signature is int -> int -> int
. In type signatures, ->
is right-associative, so that signature is really int -> (int -> int)
-- in other words, a function that takes an int and returns a function that takes an int and returns an int. But you can also think of it as a function that takes two ints and returns an int, and you can define it that way.
So, what happened with your code here is that you defined the function like this:
let get_mouse_position (relative_to : IInputElement) (args : Input.MouseEventArgs) : (int*int) = ...
This is a function that takes two parameters (of types IInputElement
and Input.MouseEventArgs
) and returns a tuple of ints... but that's equivalent to a function that takes one parameter of type IInputElement
, and returns a function that takes an Input.MouseEventArgs
and returns a tuple of two ints. In other words, your function signature is:
IInputElement -> Input.MouseEventArgs -> (int * int)
When you called it as get_mouse_position element
you passed it only a single parameter, so what you got was a function of type Input.MouseEventArgs -> (int * int)
. The type checker reported this as type 'a -> int * int
(changing Input.MouseEventArgs
into the generic type name 'a
) for reasons I don't want to get into here (this is already rather long), but that's why you got the result you got.
I hope this helps you understand F# functions better. And do read those two articles I linked to; they'll take your understanding another step further.
Upvotes: 2
Reputation: 349
I solved it by using the static Mouse.GetPosition method to obtain the position of the mouse instead of Input.MouseEventArgs.
The code now looks as follows, if anyone else has the same problem:
// 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
Upvotes: 0