Simple type error on parsing the result of getArgs

廉价感情. 提交于 2019-12-25 07:48:58

问题


I'm trying a very simple argument parser patterned off this example. The first argument ought to be a double, the second ought to be an integer, and if they aren't both of those types, I'd like to use default arguments for both specified in the else. Here's what I have:

parseArgs :: [String] -> (Double, Integer)
parseArgs args = do
  if length(args) == 2 then do
    let v1 = read (args !! 0) :: Double
    let v2 = read (args !! 1) :: Integer
    return (v1, v2)
  else do
    let v1 = read ("0.5") :: Double 
    let v2 = read ("5") :: Integer
    return (v1, v2)

I understand there are more sophisticated ways to parse arguments with an optparse-inspired design using Applicative, but I'm not there yet.

Here's the error I get:

myscript.hs:186:5-31: error: …
    • Couldn't match type ‘(Double, Integer)’ with ‘Integer’
      Expected type: (Double, Integer)
        Actual type: (Double, (Double, Integer))
    • In a stmt of a 'do' block: return (v1, v2)

I don't understand this. Looking at the signature of getArgs, I don't see anything weird that would suggest I can't get my int back, or that it should return (Double,Integer) rather than just Integer.

How can I do this correctly?


回答1:


It looks to me that you have some experience in the "imperative" world where return seems to be a keyword to return content from a function.

In Haskell however, the return statement is used to define/use monads. The same for the do block by the way. Your type (Int,Double) can be used as a monadic type (kudos to @duplode for that). But in this context it does not look much like you want/have to use monads, because this looks like a simple function.

So you can solve the problem with:

parseArgs :: [String] -> (Double, Integer)
parseArgs args =
  if length(args) == 2 then
    let v1 = read (args !! 0) :: Double
        v2 = read (args !! 1) :: Integer
    in (v1, v2)
  else
    let v1 = read ("0.5") :: Double 
        v2 = read ("5") :: Integer
    in (v1, v2)

So you use in to say that you use the v1 and v2, etc. in an expression. Still it is not very "Haskell-ish". A better way to do this is using guards (drop the if-else):

parseArgs :: [String] -> (Double, Integer)
parseArgs args | length args == 2 =
                     let v1 = read (args !! 0) :: Double
                         v2 = read (args !! 1) :: Integer
                     in (v1, v2)
               | otherwise =
                     let v1 = read ("0.5") :: Double 
                         v2 = read ("5") :: Integer
                     in (v1, v2)

Finally I do not really see why you use all these let statements and specify the types anyway. You can simply rewrite it to:

parseArgs :: [String] -> (Double, Integer)
parseArgs args | length args == 2 = (read (args !! 0), read (args !! 1))
               | otherwise = (read "0.5", read "5")

Now we are still not done. Because of args has length two, that means it has the shape [a,b]. We can use that pattern in the head:

parseArgs :: [String] -> (Double, Integer)
parseArgs [a,b] = (read a, read b)
parseArgs _     = (read "0.5", read "5")

The advantage is that you do no longer need to use (!!) to get the i-th element: checking and matching is done concurrently so to speak.

A last improvement I propose is to omit the read in the second case: you can simply enter 0.5 and 5:

parseArgs :: [String] -> (Double, Integer)
parseArgs [a,b] = (read a, read b)
parseArgs _     = (0.5, 5)



回答2:


You don't need to use do notation here since that is for handling some monadic type. You can just match on the input list:

parseArgs :: [String] -> (Double, Integer)
parseArgs [d, i] = (read d, read i)
parseArgs _ = (0.5, 5)


来源:https://stackoverflow.com/questions/41913695/simple-type-error-on-parsing-the-result-of-getargs

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!