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,sands1. There is no instance ofMArrayunless those two type variables are the same. This is an important part of howSTis 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 variablesin that signature is not the same as the type variablesin 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
ScopedTypeVariablesextension to allow the signature ongoto share the type variable with the enclosing definition:The
LANGUAGEpragma 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 forScopedTypeVariablesto fail to work.)After doing that in the type of
findDuplicates, it stores thatsin scope across the entire definition. When finding the type variablesin the type ofgo, it no longer treats it as a fresh type variable, and makes it match the typesin the enclosing context instead.