RankNTypes doesn't match return type
Asked Answered
D

2

2

Using RankNTypes, I define a type that doesn't depend on a type variable. Is this the right way to go around the case below?

I need to define a few functions to be used inside ST s, which, of course, doesn't depend on s. Yet, this causes a problem that an expression of Exp with two Ints applied to it doesn't result in a Block. Why?

Here is a reproducer:

import Control.Monad.ST
import Data.Vector.Unboxed (Vector)
import qualified Data.Vector.Unboxed as U
import Data.Vector.Unboxed.Mutable (STVector)
import qualified Data.Vector.Unboxed.Mutable as UM

type Exp = Int -> Int -> Block
type Block = forall s . STVector s Int -> ST s Int

block :: [Block] -> Block
block [] _ = return 0  -- mapM doesn't work, either - ok, I kinda see why
block (e:es) a = do x <- e a
                    xs <- block es a
                    return $ x+xs

copy :: Exp
copy i j a = do
        aj <- a `UM.unsafeRead` j
        UM.unsafeWrite a i aj
        return 1


f :: Block -> Vector Int -> Int
f blk ua = runST $ U.thaw ua >>= blk

g :: Block -> Int
g blk = f blk $ U.fromListN 12 [1..]

main = print . g $ block [copy 10 1]

The error I get points at the last line:

Couldn't match type `STVector s0 Int -> ST s0 Int'
              with `forall s. STVector s Int -> ST s Int'
Expected type: Block
  Actual type: STVector s0 Int -> ST s0 Int
In the return type of a call of `block'
Probable cause: `block' is applied to too few arguments
In the second argument of `($)', namely `block [copy 10 1]'
In the expression: print . g $ block [copy 10 1]

The difference between the expected and actual type is the forall s. bit, as far as I can tell.

Distraction answered 14/1, 2015 at 12:20 Comment(1)
the problem is similar to #27888407Cyclopropane
C
3

While I'd prefer the solution @Oleg posted, I'd like to share an alternative.

Replace

main = print . g $ block [copy 10 1]

with

main = print (g (block [copy 10 1]))

Reason: impredicative types make it hard for the compiler to guess the type of (.) and ($) above.

Another option would be to annotate (.) and ($) with their instantiated type -- this would be rather cumbersome, though.

Curch answered 14/1, 2015 at 13:53 Comment(1)
($) is a magic operator, so you can write main = print $ g $ block [copy 10 1] as well.Arnaldo
C
3

Using newtype for Block will keep s existential. Otherwise it will "leak" out


With original definition:

type Block = forall s . STVector s Int -> ST s Int
type Exp = Int -> Int -> Block

You could simplify failing example (main) to:

g . block

You'd like it's type to be:

g . block :: [Block] -> Int

But as the written out types of the components are:

block :: forall s. [forall s0. STVector s0 Int -> ST s0 Int] -> (STVector s Int -> ST s Int)
g :: (forall s1. STVector s1 Int -> ST s1 Int) -> Int

Then when composed with (.), GHC keeps s general:

g . block :: forall s . [forall s0. STVector s0 Int -> ST s0 Int] -> Int

and tries to unify:

forall s1. STVector s1 Int -> ST s1 Int -- and
(STVector s Int -> ST s Int)

With newtype everything works perfectly (and no need for ImpredicativeTypes):

{-# LANGUAGE RankNTypes #-}
import Control.Monad.ST
import Data.Vector.Unboxed (Vector)
import qualified Data.Vector.Unboxed as U
import Data.Vector.Unboxed.Mutable (STVector)
import qualified Data.Vector.Unboxed.Mutable as UM

type Exp = Int -> Int -> Block
newtype Block = Block { unBlock :: forall s . STVector s Int -> ST s Int }

block :: [Block] -> Block
block [] = Block $ \_ -> return 0  -- mapM doesn't work, either - ok, I kinda see why
block (e:es) = Block $ \a -> do x <- unBlock e a
                                xs <- unBlock (block es) a
                                return $ x + xs

copy :: Exp
copy i j = Block $ \a -> do
        aj <- a `UM.unsafeRead` j
        UM.unsafeWrite a i aj
        return 1


f :: Block -> Vector Int -> Int
f (Block blk) ua = runST $ U.thaw ua >>= blk

g :: Block -> Int
g blk = f blk $ U.fromListN 12 [1..]

main = print . g $ block [copy 10 1]
Cyclopropane answered 14/1, 2015 at 13:24 Comment(2)
I upvoted your answer, but kudos goes to @chi, for he explained the "why" part of the question, too.Distraction
Ah true, you could annotate the (.) in g . block to get it work with type true. But that is so cumbersome, I didn't even mentioned it :)Cyclopropane
C
3

While I'd prefer the solution @Oleg posted, I'd like to share an alternative.

Replace

main = print . g $ block [copy 10 1]

with

main = print (g (block [copy 10 1]))

Reason: impredicative types make it hard for the compiler to guess the type of (.) and ($) above.

Another option would be to annotate (.) and ($) with their instantiated type -- this would be rather cumbersome, though.

Curch answered 14/1, 2015 at 13:53 Comment(1)
($) is a magic operator, so you can write main = print $ g $ block [copy 10 1] as well.Arnaldo

© 2022 - 2024 — McMap. All rights reserved.