问题
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