Haskell - 如何处理依赖于变量的多行输入

时间:2014-08-05 15:41:53

标签: haskell io

我正在尝试解决Haskell中的竞争性编程挑战问题。

这是我的代码:

module Main (main) where

import System.IO
import Text.Printf

getInt :: IO Int
getInt = readLn

getDouble :: IO Double
getDouble = readLn

getCoordinate :: (IO Double, IO Double, IO Double)
getCoordinate = (getDouble, getDouble, getDouble)

readCoordinates :: Int -> [(IO Double, IO Double, IO Double)] -> [(IO Double, IO Double, IO Double)]
readCoordinates 0 list = list
readCoordinates a list = readCoordinates (a - 1) list ++ [getCoordinate]

main :: IO ()
main = do
  limit <- getInt
  coordinates <- (readCoordinates limit [])
  printf "%.2f\n" (run 0.0 (head coordinates) (tail coordinates))

run :: Double -> (Double, Double, Double) -> [(Double, Double, Double)] -> Double
run curr c1 (c2:cs) = run (curr + (d c1 c2)) c2 cs
run curr c1 [] = curr


d :: (Double, Double, Double) -> (Double, Double, Double) -> Double
d (x1, y1, z1) (x2, y2, z2) = sqrt (sas x1 x2) + (sas y1 y2) + (sas z1 z2)

sas :: Double -> Double -> Double
sas a1 a2 = (a1 - a2) ** 2

所以你可能猜到我正在读一个整数,它表示我应该读入多少3d坐标。然后我尝试读取所有这些并计算距离。

我收到很多错误,这里是错误日志:

Akvariet.hs:22:19:
Couldn't match type `[]' with `IO'
Expected type: IO (IO Double, IO Double, IO Double)
  Actual type: [(IO Double, IO Double, IO Double)]
In the return type of a call of `readCoordinates'
In a stmt of a 'do' block:
  coordinates <- (readCoordinates limit [])
In the expression:
  do { limit <- getInt;
       coordinates <- (readCoordinates limit []);
       printf "%.2f" (run 0.0 (head coordinates) (tail coordinates)) }

Akvariet.hs:23:34:
Couldn't match expected type `[(Double, Double, Double)]'
            with actual type `(IO Double, IO Double, IO Double)'
In the first argument of `head', namely `coordinates'
In the second argument of `run', namely `(head coordinates)'
In the second argument of `printf', namely
  `(run 0.0 (head coordinates) (tail coordinates))'

Akvariet.hs:23:53:
Couldn't match expected type `[(Double, Double, Double)]'
            with actual type `(IO Double, IO Double, IO Double)'
In the first argument of `tail', namely `coordinates'
In the third argument of `run', namely `(tail coordinates)'
In the second argument of `printf', namely
  `(run 0.0 (head coordinates) (tail coordinates))'

我实际上无法真正地围绕IO类型,我知道它是不纯的并且每次都不会返回相同的东西,但我如何在我的程序中使用它?

我不明白readCoordinates方法是如何编译的,以及当main仍然是IO ()类型时,为什么它不能将IO Double转换为Double。

干杯!

1 个答案:

答案 0 :(得分:7)

首先,我建议您读取数字,然后单独输入每个坐标。一次性读取所有输入(产生一个字符串)更容易,然后将其解析为坐标,而不必担心它是如何来自IO的。这看起来像

main = do
   allInput <- getContents
   let coordinates = parseCoords $ lines allInput
   printf ...

type Vect = (Double, Double, Double)

parseCoords :: [String] -> [Vect]
parseCoords (x:y:z:cs) = (read x, read y, read z) : parseCoords cs
parseCoords _ = []

如果您希望手动阅读所有内容,以便对订单或其他内容进行精确控制,那么您需要正确使用IO作为monad。将三个getDouble组合成一个元组的IO动作是没什么用的;你真正想要的是一个单个 IO动作,它产生一个纯粹的坐标元组。

getCoordinate :: IO Vect
getCoordinate = do
   x <- getDouble
   y <- getDouble
   z <- getDouble
   return (x,y,z)

实际上这可以用Applicative写得更好,但我怀疑你发现上面的do写作更容易理解:

getCoordinate' = liftA3 (,,) getDouble getDouble getDouble