Home > other >  Problem using impure function wrapped in pure function in Haskell
Problem using impure function wrapped in pure function in Haskell

Time:04-11

I'm trying to use the readFile function wrapped in a pure function.

Functions Used (In my school I have to re-write all built-in functions if I want to use it)

myNth :: [a] -> Int -> a
myNth [] n = error "error"
myNth (x:xs) 0 = x
myNth (_:xs) n = myNth xs (n - 1)
myTail :: [a] -> [a]
myTail (_:a) = a
myTail [] = error "empty list"
pointStrToInt :: String -> (Int, Int, Int)
pointStrToInt point = (read(myTail(myNth(split ',' point) 0)), read(myNth(split ',' point) 1), read(myNth(split ')' (myNth(split ',' point) 2)) 0))
fileToPointArray :: [String] -> [(Int, Int, Int)]
fileToPointArray = map pointStrToInt
split :: Eq a => a -> [a] -> [[a]]
split d [] = []
split d s = x : split d (drop 1 y) where (x,y) = span (/= d) s

Problem Function:

closest :: FilePath -> (Int, Int, Int) -> (Int, Int, Int) 
closest path a = do
                    let content = readFile path
                    let closestPoint = pointStrToInt (myNth(lines (content)) 0)
                    --print (closestPoint)
                    let arr = fileToPointArray (lines content)
                    --print (arr)
                    let p = doClosest arr a closestPoint
                    --print (p)
                    p

    where
        doClosest :: [(Int, Int, Int)] -> (Int, Int, Int) -> (Int, Int, Int) -> (Int, Int, Int)
        doClosest [] _ a = a
        doClosest (x:xs) a (b, c, d) = if ((distance (itofPoint x) (itofPoint a)) < (distance (itofPoint x) (itofPoint (b, c, d))))
                                       then (doClosest xs a x)
                                       else (doClosest xs a (b, c, d))

The main objective is get the closest point to a given one passed as parameter. The prototype must be something like:

closest :: FilePath -> (Int, Int, Int) -> (Int, Int, Int)

But I can't use a impure function (readFile) due to the function is not IO().

How could i fix it?

Edit: -The file contains a set of points in (x,y,z) format

-The distance function return the distance of the 2 points

-The file can change in any execution

CodePudding user response:

You are already in a do bock; you just aren't taking advantage of it. content should be extracted from readFile path, not simply reference the IO action it creates. You'll also need to change the return value to IO (Int, Int, Int):

closest :: FilePath -> (Int, Int, Int) -> IO (Int, Int, Int) 
closest path a = do
                    content <- readFile path
                    let closestPoint = pointStrToInt (myNth(lines (content)) 0)
                    let arr = fileToPointArray (lines content)
                    let p = doClosest arr a closestPoint
                    return p

    where
        doClosest :: [(Int, Int, Int)] -> (Int, Int, Int) -> (Int, Int, Int) -> (Int, Int, Int)
        doClosest [] _ a = a
        doClosest (x:xs) a (b, c, d) = if ((distance (itofPoint x) (itofPoint a)) < (distance (itofPoint x) (itofPoint (b, c, d))))
                                       then (doClosest xs a x)
                                       else (doClosest xs a (b, c, d))

A little simpler is to keep closest pure and combine it with readFile in a wrapper.

-- Note the order of arguments is reversed, so that we
-- can partially apply it later...
closest :: (Int, Int, Int) -> String -> (Int, Int, Int) 
closest content a = let closestPoint = pointStrToInt (myNth(lines (content)) 0)
                        arr = fileToPointArray (lines content)
                    in doClosest arr a closestPoint

    where
        doClosest :: [(Int, Int, Int)] -> (Int, Int, Int) -> (Int, Int, Int) -> (Int, Int, Int)
        doClosest [] _ a = a
        doClosest (x:xs) a (b, c, d) = if ((distance (itofPoint x) (itofPoint a)) < (distance (itofPoint x) (itofPoint (b, c, d))))
                                       then (doClosest xs a x)
                                       else (doClosest xs a (b, c, d))

doIt :: FilePath -> (Int, Int, Int) -> IO (Int, Int, Int)
doIt path a = fmap (closest a) (readFile path)

(If you don't want to or can't alter the argument order in closest, you can use fmap (flip closest a) (readFile path) instead.)

  • Related