Search code examples
arrayshaskellstate-monadstarray

Two almost identical functions using STArray: why does one requires FlexibleContexts, and the other does not?


Consider the Haskell functions

test :: ST s [Int]
test = do
    arr <- newListArray (0,9) [0..9] :: ST s (STArray s Int Int)
    let f i = do writeArray arr i (2*i)
                 readArray arr i 
    forM [1,2] f

and

test' :: ST s [Int]
test' = do
    arr <- newListArray (0,9) [0..9] :: ST s (STArray s Int Int)
    let f = \i -> do writeArray arr i (2*i)
                     readArray arr i
    forM [1,2] f

The first requires FlexibleContexts to compile on ghci 8.10.1, the second compiles with no extra options. Why?

An answer that explains this behaviour in terms of the scope of the type variable s would be especially welcome. As a follow up, what (if any) type signature can be added to the function f to make test compile without FlexibleContexts? Finally, is there a connection with the monomorphism restriction?


Solution

  • You can check which type GHC assigns to f in GHCi:

    ghci> import Data.Array
    ghci> import Data.Array.MArray
    ghci> let arr :: STArray s Int Int; arr = undefined
    ghci> :t \i -> do writeArray arr i (2*i); readArray arr i
    \i -> do writeArray arr i (2*i); readArray arr i
      :: (MArray (STArray s1) Int m, MArray (STArray s2) Int m) =>
         Int -> m Int
    

    This is more general than the type you suggest in your comments and the reason that FlexibleContexts are needed.

    You can add the type signature you suggested (Int -> ST s Int) to avoid having to use FlexibleContexts:

    {-# LANGUAGE ScopedTypeVariables #-}
    
    ...
    
    test :: forall s. ST s [Int]
    test = do
        arr <- newListArray (0,9) [0..9] :: ST s (STArray s Int Int)
        let 
          f :: Int -> ST s Int
          f i = do
            writeArray arr i (2*i)
            readArray arr i 
        forM [1,2] f
    

    Note that scoped type variables and the forall s. are necessary here because you need to make sure that the s in all the type signatures refer to the same type variable and do not all introduce new type variables.

    The reason that the monomorphism restriction treats your first and your second version differently is because it doesn't apply to things that look like functions. In your first version f has an argument, so it looks like a function and therefore will get a general type. In your second version f doesn't have arguments, so it doesn't look like a function which means that the monomorphism restriction forces it to have a more specific type.