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

Joh*_*n S 0 io haskell

我正在尝试解决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
Run Code Online (Sandbox Code Playgroud)

所以你可能会猜到我正在读一个整数,它表示我应该读入多少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))'
Run Code Online (Sandbox Code Playgroud)

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

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

干杯!

lef*_*out 7

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

main = do
   allInput <- getContents
   let coordinates = parseCoords $ lines allInput
   printf ...
Run Code Online (Sandbox Code Playgroud)

type Vect = (Double, Double, Double)

parseCoords :: [String] -> [Vect]
parseCoords (x:y:z:cs) = (read x, read y, read z) : parseCoords cs
parseCoords _ = []
Run Code Online (Sandbox Code Playgroud)

如果您希望手动阅读所有内容,以便精确控制订单或其他任何内容,那么您需要正确使用IOmonad.将三个getDoubles 组合成IO动作元组几乎没有用处; 你真正想要的是一个单一的 IO操作这将产生一个纯粹的坐标元组.

getCoordinate :: IO Vect
getCoordinate = do
   x <- getDouble
   y <- getDouble
   z <- getDouble
   return (x,y,z)
Run Code Online (Sandbox Code Playgroud)

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

getCoordinate' = liftA3 (,,) getDouble getDouble getDouble
Run Code Online (Sandbox Code Playgroud)

  • 另一种使用`Applicative`的方法:`getCoordinate =(,,,)<$> getDouble <*> getDouble <*> getDouble`.我指出这一点的主要原因是他们想知道如何将它扩展到任意数量的参数. (3认同)