I have been learning Haskell for the past month or two, and recently solved this coding problem. The additional challenge was to do the task without extra space and in linear time, which I did not think would be possible to do in a purely functional way, so naturally I found out about the ST monad and I thought this would be a good opportunity to learn more about it. Anyways, here is the code that I wrote:
module FindDuplicates where
import Control.Monad (foldM)
import Control.Monad.ST
import Data.Array.ST
xs = [4,3,2,7,8,2,3,1] :: [Int]
findDuplicates :: [Int] -> ST s [Int]
findDuplicates xs = do
arr <- newListArray (1, length xs) xs :: ST s (STArray s Int Int)
let go :: [Int] -> Int -> ST s [Int]
go acc i = do x <- abs <$> readArray arr i
y <- readArray arr x
if y < 0
then return (x:acc)
else do writeArray arr x (-y)
return acc
foldM go [] [1..length xs]
The idea is to use the pre-condition that 1 ≤ a[i] ≤ n and that each element appears at most 2 times. But the code gives me the following error.
FindDuplicates.hs:14:36:
No instance for (MArray (STArray s) Int (ST s1))
arising from a use of ‘readArray’
In the second argument of ‘(<$>)’, namely ‘readArray arr i’
In a stmt of a 'do' block: x <- abs <$> readArray arr i
In the expression:
do { x <- abs <$> readArray arr i;
y <- readArray arr x;
if y < 0 then
return (x : acc)
else
do { writeArray arr x (- y);
.... } }
I hope someone can point me in the right direction!
In
No instance for (MArray (STArray s) Int (ST s1))
, the most important thing to notice is that it's talking about two different type variables,s
ands1
. There is no instance ofMArray
unless those two type variables are the same. This is an important part of howST
is valid with an externally-pure interface.The reason the compiler thinks that there are two different type variables involved is that you put a type signature on
go
. The type variables
in that signature is not the same as the type variables
in the signature offindDuplicates
. This is an inherent part of Haskell's type signature rules - type variables in any particular signature are unrelated to type variables in any other signature.The simplest way to fix this is to remove the signature from
go
. Type inference will get the correct type for it.If you want to get fancier, you can use the
ScopedTypeVariables
extension to allow the signature ongo
to share the type variable with the enclosing definition:The
LANGUAGE
pragma at the top enables the extension. To use the extension, you need to specify the type variables in a definition withforall
. (Forgetting to do that is the most common reason forScopedTypeVariables
to fail to work.)After doing that in the type of
findDuplicates
, it stores thats
in scope across the entire definition. When finding the type variables
in the type ofgo
, it no longer treats it as a fresh type variable, and makes it match the types
in the enclosing context instead.