type class functions as function arguments

In my app, I am trying to save a whole bunch of code duplication by leverage the fact that functions can be passed in to other functions as arguments. However, I run into a kink when I try to use functions from type classes, and then apply those functions to different types of arguments after the functions have been passed in. My actual code is rather long and somewhat complicated, so I will use this simpler example: Obviously both the following functions work fine: code: -------- g :: (Integer, Double) g = ((+) 1 2, (+) 2.0 3.0) g' :: (Integer, Double) g' = ((-) 1 2, (-) 2.0 3.0) *Main> g (3,5.0) *Main> g' (-1,-1.0) -------- However, what if I want to create a single function, where I just pass in the (+) or the (-) function? I not sure the right way to do that, exactly, but I tried something like this: code: -------- gCore :: Num a => (a -> a -> a) -> (Integer, Double) gCore f = (f 1 2, f 2.0 3.0) -------- The resulting error is: code: -------- Couldn't match type `Double' with `Integer' In the return type of a call of `f' In the expression: f 1 2 In the expression: (f 1 2, f 2.0 3.0) -------- It is as though the function "forgot" that it could be used on different types, and just picked one, and now is complaining that is being used with the wrong type in the other case. -- frigidcode.com indicium.us

On Thu, Sep 13, 2012 at 8:23 PM, Christopher Howard < christopher.howard@frigidcode.com> wrote:
code: -------- gCore :: Num a => (a -> a -> a) -> (Integer, Double) gCore f = (f 1 2, f 2.0 3.0) --------
The resulting error is: Couldn't match type `Double' with `Integer' In the return type of a call of `f' In the expression: f 1 2 In the expression: (f 1 2, f 2.0 3.0)
In standard Haskell, when you write a type signature, there's an implicit universal quantification of all its type variables: gCore :: forall a. (Num a => (a -> a -> a) -> (Integer, Double)) The problem comes from the parentheses. You have to get past the forall, and you can only do that by picking a value for the type variable. Another way of looking at it is that you aren't actually required to have a function (a -> a -> a) that works for *all* types a in Num; it only has to work for whatever type you happened to choose for a. In this case, that's not useful to you, since (+) is defined for all types a in Num, but according to this type signature, the caller is able to pick a type a = Foo and then provide a function of type Foo -> Foo -> Foo. To get what you want, you need this type signature instead: gCore :: (forall a. (Num a => (a -> a -> a))) -> (Integer, Double) Since the forall only covers the type of the parameter, not gCore, the body of gCore isn't committed to a particular value of a. You only have to commit to a value of a when using the parameter. This also means that the person calling gCore has to pass in a function that works for all types a in Num, rather than just for one, because when they pass that parameter, the value of a hasn't been chosen yet, since we didn't have to go inside that forall to get to the point of passing that parameter. This is not standard Haskell, but it is possible with a language extension supported by GHC called RankNTypes. (Use -XRankNTypes when running ghc or ghci.) There are downsides to using this feature which I am not qualified to fully explain, but the most common problem you'll find is that type inference fails and an explicit signature is required. (There is also an extension called Rank2Types which covers this particular case, since the forall is only one level deeper than normally allowed.) -Karl
participants (2)
-
Christopher Howard
-
Karl Voelker