Question

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
Was it helpful?

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

OTHER TIPS

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.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top