Search code examples
haskellcommand-line-argumentsyahoohaskell-platformyahoo-finance

Haskell Yahoo Finance Command Line Tool


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

Solution

  • 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..."