So, I have been trying to use SDL to make a simple GUI. This is so that I start to understand how to use haskell. In this case, I was using https://github.com/palf/haskell-sdl2-examples/blob/master/examples/lesson04/src/Lesson04.hs as reference.
Pay attention, in particular to payloadToIntent
on line 72.
payloadToIntent :: SDL.EventPayload -> Intent
payloadToIntent SDL.QuitEvent = Quit
payloadToIntent (SDL.KeyboardEvent k) = getKey k
payloadToIntent _ = Idle
This works perfectly. However, when I change the code to the following, it produces an error. Why does it happen, as to my (admittedtly novice) eyes, this looks equivalent.
payloadToIntent e
| e == SDL.QuitEvent = Quit
| e == SDL.KeyboardEvent k = getKey SDL.KeyboardEvent k
| otherwise = Idle
Error:
src/Events/Intent.hs:15:28: error:
Variable not in scope: k :: SDL.KeyboardEventData
|
15 | | e == SDL.KeyboardEvent k = getKey SDL.KeyboardEvent
| ^
I am using these language extensions: OverloadedStrings, GADTs, PatternGuards
So why did this happen? How could I fix this? Which one would be more idiomatic haskell?
CodePudding user response:
(==)
is a function that takes two values of the same type and compares them for equality, returning a Bool. SDL.KeyboardEvent k
is not a value of any type (since k
is unbound), so you can't compare it with (==)
.
The idiomatic "choice" is the one that works, i.e. pattern matching. If you want something that has a similar appearance, you can pattern match with case
...of
instead:
payloadToIntent e = case e of
SDL.QuitEvent -> Quit
SDL.KeyboardEvent k -> getKey k
_ -> Idle
CodePudding user response:
The key idea here is: patterns define variables, bringing them into scope, while expressions do not, requiring all the variables in them to be already defined.
The guard e == SDL.KeyboardEvent k
is a boolean valued expression, not a pattern. This is calling function (==)
with two arguments: e
and SDL.KeyboardEvent k
. Your definition, to the compiler, looks like:
payloadToIntent e
| isEqual e SDL.QuitEvent = Quit
| isEqual e (SDL.KeyboardEvent k) = getKey SDL.KeyboardEvent k
| otherwise = Idle
The compiler can not call the equality-test function without passing it the arguments. For that, it needs variable k
to be in scope, i.e., to be defined somewhere else.
To stress the point, consider this non-working code:
isSquare :: Int -> String
isSquare n | n == m*m = "It's the square of " show m
| otherwise = "It isn't a square"
This would magically invert the squaring, if possible. That is, however, asking too much to the compiler, which won't magically solve the equation for us. (Indeed, the solution could even fail to be unique!)
As an even more cumbersome case:
f x | x == F y || x == G z = ...
Even if this worked, can we use y
or z
in the final ...
? Probably not. Why should then this be allowed?
Finally, note that, even in those cases where it could work, allowing expressions guards to define variables could be a bad idea. Consider this:
c :: Int
c = 7
f x | x == F c = "Hi"
| otherwise = "there"
Now, is the c
in F c
a new local variable which is defined on the spot, or is it the constant 7
defined above? If we call f (F 6)
do we get Hi
(c
was a new variable) or there
(c
was 7
)?
Pattern matching avoids this issue by requiring a distinct syntax.