在 Haskell 中使用包含在纯函数中的不纯函数的问题

Problem using impure function wrapped in pure function in Haskell

我正在尝试使用包装在纯函数中的 readFile 函数。

使用的函数(在我的学校,如果我想使用它,我必须重新编写所有内置函数)

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

问题函数:

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))

主要的 objective 是获取最接近作为参数传递的给定点的点。 原型必须是这样的:

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

但我不能使用不纯函数 (readFile),因为该函数不是 IO()。

我该如何解决?

编辑: - 该文件包含一组 (x,y,z) 格式的点

-距离函数return2个点的距离

-文件可以在任何执行中更改

您已经进入 do 状态;你只是没有利用它。 content 应该从 readFile path 中提取,而不是简单地引用它创建的 IO 操作。您还需要将 return 值更改为 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))

更简单一点的是保持 closest 纯净,并将其与 readFile 结合在一个包装器中。

-- 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)

(如果您不想或不能更改 closest 中的参数顺序,您可以改用 fmap (flip closest a) (readFile path)。)