So I have this function in my code
type Player = Char
choose :: Player -> a -> a -> a
choose p a b
| p == 'X' = a
| p == 'O' = b
At one point it is used like so
(choose p (+) (-)) 5 5
I can clearly see that it chooses between the (+) or (-) function and then applies it on 5 and 5. In fact I even tested it in GHCi for myself.
However, here are the type signatures for the aforementioned functions
(+) :: Num a => a -> a -> a
(-) :: Num a => a -> a -> a
I simply do not understand how a function of this type signature can be passed to choose. Shouldn't choose have a type signature like this?
choose :: Num a => Player -> (a -> a -> a) -> (a -> a -> a) -> (a -> a -> a)
Could someone help me shine a light on this?
You need to remember that type variables can be instantiated on each use, and can be instantiated to complicated types that themselves have multiple parts! A function type like
a -> a -> ais just as good a type to substitute for type variables as isInt, or[Char], or(Double, Double), etc.So if we instantiate the
atype variable toCharwe get this:And if we instantiate the
atype variable toa -> a -> a, then we get this:So anytime you see a function's type taking an argument or returning a result that is a simple type variable, like
id :: a -> a, that does not mean you can't apply it to functions! Much more complicated-looking things like(t -> Int -> [t]) -> (t -> Int -> [t])are particular examples of the full generality of what you can use a simple type likea -> afor.You need to learn to see function types as not particularly special; they are just one of many kinds of types that exist, and anything that claims to work for all types must work for function types too.
Also remember that type variables in two different type signatures are in different scopes. The variable
ain the originalchoose :: Player -> a -> a -> ais not the same variableain(+) :: Num a => a -> a -> aor even the one inchoose :: Num a => Player -> (a -> a -> a) -> (a -> a -> a) -> (a -> a -> a). If it helps, while you're working things out you can always rename variables across all of the type signatures you're considering (and then optionally rename the variables in the final one to match an external source like GHC). For example I could avoid confusion by saying that I'm considering(+) :: Num b => b -> b -> b, and then substituting that forainPlayer -> a -> a -> ato getNum b => Player -> (b -> b -> b) -> (b -> b -> b) -> (b -> b -> b).