Haskell Yahoo Finance Command Line Tool
-
25-05-2021 - |
Domanda
I am trying to use this module to create a command line tool that will grab a quote from yahoo finance for a symbol. When I try to compile I receive this error.
Couldn't match expected type `[t0]'
with actual type `IO
(Maybe (Map (QuoteSymbol, QuoteField) QuoteValue))'
In the return type of a call of `getQuote'
In a stmt of a 'do' expression:
q <- getQuote [arg] ["s", "l1", "c"]
In the expression:
do { q <- getQuote [arg] ["s", "l1", ....];
case q of {
Nothing -> error "symbol not found"
Just m -> m } }
I am just starting to learn haskell, which is an incredibly different but powerful language, and I can not quite figure out what the problem is. Any help would be greatly appreciated and I would also appreciate any feedback on if I'm doing this the "haskell" way and if not any improvements I can make. Thanks!
module Main where
import Finance.Quote.Yahoo
import Data.Time.Calendar
import Data.Map
import System( getArgs )
import System.Console.GetOpt
import Data.Maybe ( fromMaybe )
data Options = Options
{ optVerbose :: Bool
, optShowVersion :: Bool
, optOutput :: Maybe FilePath
, optInput :: Maybe FilePath
, optLibDirs :: [FilePath]
, optSymbol :: String
} deriving Show
defaultOptions = Options
{ optVerbose = False
, optShowVersion = False
, optOutput = Nothing
, optInput = Nothing
, optLibDirs = []
, optSymbol = "YHOO"
}
options :: [OptDescr (Options -> Options)]
options =
[ Option ['v'] ["verbose"]
(NoArg (\ opts -> opts { optVerbose = True }))
"chatty output on stderr"
, Option ['V','?'] ["version"]
(NoArg (\ opts -> opts { optShowVersion = True }))
"show version number"
, Option ['o'] ["output"]
(OptArg ((\ f opts -> opts { optOutput = Just f }) . fromMaybe "output")
"FILE")
"output FILE"
, Option ['c'] []
(OptArg ((\ f opts -> opts { optInput = Just f }) . fromMaybe "input")
"FILE")
"input FILE"
, Option ['L'] ["libdir"]
(ReqArg (\ d opts -> opts { optLibDirs = optLibDirs opts ++ [d] }) "DIR")
"library directory"
, Option ['s'] ["symbol"]
(ReqArg (\ s opts -> opts { optSymbol = getSymbol s }) "SYMBOL")
"symbol SYMBOL"
]
compilerOpts :: [String] -> IO (Options, [String])
compilerOpts argv =
case getOpt Permute options argv of
(o,n,[] ) -> return (foldl (flip id) defaultOptions o, n)
(_,_,errs) -> ioError (userError (concat errs ++ usageInfo header options))
where header = "Usage: ic [OPTION...] files..."
main = do
args <- getArgs
compilerOpts args
getSymbol :: String -> String
getSymbol arg = do
q <- getQuote [arg] ["s", "l1", "c"]
case q of
Nothing -> error "symbol not found"
Just m -> m
Soluzione
The trouble is that getSymbol
has no business in the definition of options
, since it is an IO
action as we can see from the fact that it uses getQuote
:
getQuote :: [QuoteSymbol]
-> [QuoteField]
-> IO (Maybe (Map (QuoteSymbol, QuoteField) QuoteValue))
So where you say optSymbol = getSymbol s
you should just put the string s. The crucial line should certainly read
(ReqArg (\ s opts -> opts { optSymbol = s }) "SYMBOL")
not
(ReqArg (\ s opts -> opts { optSymbol = getSymbol s }) "SYMBOL")
At the moment you are just parsing arguments. What you do with the optSymbol
string -- e.g. go to Yahoo to find out about it via getSymbol
-- belongs in main
or some preliminary action you need to define. Here is a module that typechecks, it barely does anything, acting like this:
-- $ ./yahoo -s STD.F -- Standard Chartered?
-- STD.F c -0.396 - -1.99%
-- STD.F l1 19.492
-- STD.F s STD.F
module Main where
import Finance.Quote.Yahoo
import Data.Time.Calendar
import Data.Map
import Prelude
import qualified Prelude
import System.Environment( getArgs )
import System.Console.GetOpt
import Data.Maybe ( fromMaybe )
import System.IO.Unsafe
main = do
args <- getArgs
(options, strs) <- compilerOpts args
whatIDoWithTheUsersOptions options strs
-- This is not doing much with all this user input ...
whatIDoWithTheUsersOptions :: Options -> [String] -> IO ()
whatIDoWithTheUsersOptions options strs = do
blather <- getSymbol $ optSymbol options
putStrLn blather
getSymbol :: String -> IO String
getSymbol arg = do
q <- getQuote [arg] ["s", "l1", "c"]
case q of
Nothing -> return $ "symbol " ++ arg ++ " not found"
Just m -> return $ unlines $ Prelude.map helper (toList m)
where helper ((a,b), c) = unwords [a,b,c]
data Options = Options
{ optVerbose :: Bool
, optShowVersion :: Bool
, optOutput :: Maybe FilePath
, optInput :: Maybe FilePath
, optLibDirs :: [FilePath]
, optSymbol :: String
} deriving Show
defaultOptions = Options
{ optVerbose = False
, optShowVersion = False
, optOutput = Nothing
, optInput = Nothing
, optLibDirs = []
, optSymbol = "YHOO"
}
options :: [OptDescr (Options -> Options)]
options =
[ Option ['v'] ["verbose"]
(NoArg (\ opts -> opts { optVerbose = True }))
"chatty output on stderr"
, Option ['V','?'] ["version"]
(NoArg (\ opts -> opts { optShowVersion = True }))
"show version number"
, Option ['o'] ["output"]
(OptArg ((\ f opts -> opts { optOutput = Just f }) . fromMaybe "output")
"FILE")
"output FILE"
, Option ['c'] []
(OptArg ((\ f opts -> opts { optInput = Just f }) . fromMaybe "input")
"FILE")
"input FILE"
, Option ['L'] ["libdir"]
(ReqArg (\ d opts -> opts { optLibDirs = optLibDirs opts ++ [d] }) "DIR")
"library directory"
, Option ['s'] ["symbol"]
(ReqArg (\ s opts -> opts { optSymbol = s }) "SYMBOL")
"symbol SYMBOL"
]
compilerOpts :: [String] -> IO (Options, [String])
compilerOpts argv =
case getOpt Permute options argv of
(o,n,[] ) -> return (Prelude.foldl (flip id) defaultOptions o, n)
(_,_,errs) -> ioError (userError (concat errs ++ usageInfo header options))
where header = "Usage: ic [OPTION...] files..."
Altri suggerimenti
This is wrong:
getSymbol :: String -> String
getSymbol arg = do
q <- getQuote [arg] ["s", "l1", "c"]
case q of
Nothing -> error "symbol not found"
Just m -> m
It should probably be:
getSymbol :: String -> IO String
getSymbol arg = do
q <- getQuote [arg] ["s", "l1", "c"]
case q of
Nothing -> fail "symbol not found"
Just m -> return m
Note I have changed the type signature and the final two lines. (Since I have changed the type of getSymbol
, you will have to use the result differently wherever you call it.)
A partial explanation:
You are using do
syntax, which results in a monadic value. The return type of getSymbol
was String
, which is the same as [Char]
. Lists are monads, so this is fine.
But then you call getQuote
on the right of a <-
, which results in an IO
value. IO
is monadic, but IO
is not the same as lists, so we have an error. (You have to use the same monad on the right hand side of a <-
as the do
block results in.)
Edit: The above code change isn't going to be enough. m
has type Map (QuoteSymbol, QuoteField) QuoteValue
which obviously isn't the same as String
, so you want to compute something from m
instead of returning it directly. I don't know exactly what you want to do here.
Also, you're using getSymbol
in your option parsing code. It's better if your save the input to getSymbol
in your Options
, and call getSymbol
later with that value, after you have processed the command line options. (Otherwise you might contact Yahoo's servers only to discover that a different command line option is wrong and throw the answer away. Not to mention that the optSymbol
field can't hold IO
.)