17

Say I have a function:

f :: Int -> (Rational, Integer)
f b = ((toRational b)+1,(toInteger b)+1)

I want to abstract away the (+1) like so:

f :: Int -> (Rational, Integer)
f b = (h (toRational b)
      ,h (toInteger b))
    where h = (+1)

This wont work obviously, but if I specify the type signature it will work:

f :: Int -> (Rational, Integer)
f b = (h (toRational b)
      ,h (toInteger b))
    where h :: Num a => a -> a
          h = (+1)

Say I now want to further abstract the function by passing h as a parameter:

f :: Num a => Int -> (a -> a) -> (Rational, Integer)
f b g = (h (toRational b)
        ,h (toInteger b))
    where h :: Num a => a -> a
          h = g

I get an error that the inner a is not the same a as the outer one.

Does anyone know how to write this function correctly? I want to pass a polymorphic function g to f and use it polymorphically.

I have encountered this situation multiple times now in very different projects, and I could not find a good solution.

false
  • 10,264
  • 13
  • 101
  • 209
nulvinge
  • 1,600
  • 8
  • 17
  • 1
    Welcome to the world of *higher ranked types*! – Ingo Feb 14 '13 at 08:31
  • 6
    "This wont work obviously" Actually, it's not obvious that this won't work: `(+1)` is polymorphic, only the [dreaded monomorphism restriction](http://www.haskell.org/haskellwiki/Monomorphism_restriction) prevents `h` from inheriting this trait. If you set `-XNoMonomorphismRestriction`, your second code box works fine. – leftaroundabout Feb 14 '13 at 10:29

1 Answers1

18

I found the solution: using the forall quantifier like so:

{-# LANGUAGE RankNTypes #-}
f :: Int -> (forall a. Num a=> a -> a) -> (Rational, Integer)
f b g = (h (toRational b)
        ,h (toInteger b))
    where h :: Num a => a -> a
          h = g

Which of course can be turned into:

f :: Int -> (forall a. Num a=>a -> a) -> (Rational, Integer)
f b g = (g (toRational b)
        ,g (toInteger b))
nulvinge
  • 1,600
  • 8
  • 17
  • 11
    This is exactly right. Without the forall Haskell will try to specialize the function argument within the function body. This leads to the unsolvable system `(Rational ~ a) && (Integer ~ a)`. The forall binding says that you're going to pass in an unspecialized function and specialize it only at the call site. The downside is that you can't pass another `a` type in and be sure it matches the function—not that you would do that in this situation. – J. Abrahamson Feb 14 '13 at 00:43