1
votes

I'm having a bit of trouble with my types lining up in this code:

distance :: (Floating a) => (a, a) -> (a, a) -> a
distance (x1, y1) (x2, y2) = sqrt $ (x2 - x1)**2 + (y2 - y1)**2

gravitation :: (Num a, Floating a) => (a,a) -> (a,a) -> a
gravitation = ((**) (-1.0)) . ((**) 2.0) . distance

I was under the impression that since distance returns a Floating variables, I could pipe it into the ((**) 2.0). Is there a fix for this whilst keeping the code relatively elegant?

The error I get is:

Main.hs:13:16:
    Could not deduce (Floating ((a, a) -> a))
      arising from a use of `**'
    from the context (Num a, Floating a)
      bound by the type signature for
                 gravitation :: (Num a, Floating a) => (a, a) -> (a, a) -> a
      at Main.hs:12:16-57
    Possible fix:
      add an instance declaration for (Floating ((a, a) -> a))
    In the first argument of `(.)', namely `((**) (- 1.0))'
    In the expression: ((**) (- 1.0)) . ((**) 2.0) . distance
    In an equation for `gravitation':
1
A function of two arguments is really a function of one argument, returning another function of one argument. The upshot is that (.) really only works to compose functions of one (remaining) argument; you can do tricks to get more but it gets more complex. - luqui
So is the issue that it's basically trying to pipe a function of type (a,a) -> a into ((**) 2.0) instead of my desired double? - Tombert
@Tombert correct. Try doing :t ((**) 2.0) . distance in GHCi, it'll derive a type for you, but it'll be nonsensical with Floating ((a, a) -> a) as a constraint. Try :t (. distance), the type it'll return will be all sorts of crazy. Then try :t \x -> ((**) 2.0) . distance x, the type will now make sense and look like you want it to. - bheklilr
@Tombert Remember, you can eta-reduce f x = h $ g x to f = h . g, but you can't reduce f x y = h $ g x y to f x = h . g x to f = h . g, since in the second example you reduce the $ to . with the y parameter. That's essentially what's happening here, and why a new combinator is needed. - bheklilr

1 Answers

2
votes

The problem is that distance takes two arguments, you need a composition operator that works with two. There's a package out there that has handy ones defined, but it's pretty easy to remember in a pinch:

infixr 9 .:
(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(.:) = (.).(.)

Now you should be able to do

gravitation = (negate 1 **) . (2 **) .: distance

The infixr 9 is important, it sets the fixity of the .: operator to be the same as the . operator. I always just quickly check this in GHCi with :i (.) because who has time to remember these things? Whenever I see this problem, I always like to point out that this operator has a more general form using fmap:

(.:) :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
(.:) = fmap fmap fmap

Technically, the first of those fmaps is for the function functor, so it's the same as fmap . fmap as well. This more general form simply fmaps a function 2 layers deep, and in the case of functions being your functors it turns out to be this handy composition operator. It has other uses, though:

(+1) .: [Just 1, Nothing, Just 3]
[Just 2, Nothing, Just 4]

Again, it just applies (+1) two functor layers deep, that's it.