3

I would like to be able to write an IO-action, which would return a polymorphic function, which could be used for different values, but it seems I can't. Can anyone help me doing that please?

f :: a -> Bool
f _ = True

getF :: Int -> (a -> Bool)
getF _ = f

getFIO :: IO (a -> Bool)
getFIO = return f

main :: IO ()
main = do
    -- this works
    print ((f :: Int -> Bool) (10::Int))
    print ((f :: String -> Bool) "asd")

    -- this works
    let f' = getF 10
    print (f' (10::Int))
    print (f' "asd")

    -- this doesn't
    f'' <- getFIO
    print (f'' (10::Int))
    print (f'' "asd")

    -- but this does
    f''' <- getFIO
    print (f''' (10::Int))
    f'''' <- getFIO
    print (f'''' "asd")

    return ()
Konstantine Rybnikov
  • 2,457
  • 1
  • 22
  • 29
  • 2
    Related: http://stackoverflow.com/questions/33092864/what-is-predicativity – dfeuer Oct 19 '15 at 17:30
  • 1
    A function of type `a -> Bool` can't do anything useful that a value of type `Bool` can't, so I'm not sure why you want to do this. – Rein Henrichs Oct 19 '15 at 18:10
  • Specifically, it has to work uniformly for all choices of `a` (parametricity), so the only (defined) inhabitants are `const True` and `const False`. – Rein Henrichs Oct 19 '15 at 18:20
  • @ReinHenrichs it's a simplified program which reproduces error. In real-world usage, that function looks a bit different and has few type-class constraints which let you examine input-argument in many more ways. – Konstantine Rybnikov Oct 19 '15 at 18:36

3 Answers3

8

Haskell does not allow you to use a polymorphic argument to a type constructor other than ->. You need to use a newtype to make use of that special exemption.

{-# LANGUAGE RankNTypes #-}
newtype ToBool = ToBool {getToBool :: forall a . a -> Bool}

f :: ToBool
f = ToBool (const True)

getFIO :: IO ToBool
getFIO = return f

You'll need to apply getToBool each time you want to use f, but that's okay.

dfeuer
  • 48,079
  • 5
  • 63
  • 167
5

To complement the other answers, I'd note that

f :: IO (a -> Bool)

actually means

f :: forall a. IO (a -> Bool)

which describes the following contract between caller and callee:

  1. The caller chooses a type a
  2. Then, f does some IO
  3. Finally, f returns a value of type a -> Bool

Note that a is chosen before the IO, hence the last function is monomorphic.

Instead, we need the following type

f :: IO (forall a. a -> Bool)

which postpones the choice of a after the IO is done. The above type requires the ImpredicativeTypes extension (which does not really work at the moment in GHC), or using a newtype wrapper as done in the other answers.

chi
  • 111,837
  • 3
  • 133
  • 218
4

You can make it "work" with RankNTypes, but then you need a newtype wrapper:

-- might as well be Bool
newtype Pointless = Pointless (forall a. a -> Bool)

getFIO_ :: IO Pointless
getFIO_ = return (Pointless f)

main = do
    Pointless f'' <- getFIO_
    print (f'' (10::Int))
    print (f'' "asd")

So you might be better off rearranging your program to not use that extension, by changing getFIO :: IO Blah, defining a applyBlah :: Blah -> a -> Bool, and then using applyBlah f'' where you previously had f''.

aavogt
  • 1,308
  • 6
  • 14