I get an IO String via:
import Data.Char
import Network.HTTP
import Text.HTML.TagSoup
openURL :: String -> IO String
openURL x = getResponseBody =<< simpleHTTP (getRequest x)
crawlType :: String -> IO String
crawlType pkm = do
src <- openURL url
return . fromBody $ parseTags src
where
fromBody = unwords . drop 6 . take 7 . words . innerText . dropWhile (~/= "<p>")
url = "http://pokemon.wikia.com/wiki/" ++ pkm
and I want to parse its data via:
getType :: String -> (String, String)
getType pkmType = (dropWhile (== '/') $ fst b, dropWhile (== '/') $ snd b)
where b = break (== '/') pkmType
But like you see, getType doesn't support the IO String yet.
I'm new to IO, so how to make it working?
I also tryed to understand the error when giving the IO String to that function, but it's too complicated for me up to now :/
First, to emphasize: an IO String is not a string. It's an IO action which, when you bind it somewhere within the main action, will yield a result of type String, but you should not think of it as some sort of “variation on the string type”. Rather, it's a special instantiation of the IO a type.
For this reason, you almost certainly do not want to “change a function to support IO String instead of String”. Instead, you want to apply this string-accepting function, as it is, to an outcome of the crawlType action. Such an outcome, as I said, has type String, so you're fine there. For instance,
main :: IO ()
main = do
pkm = "blablabla"
typeString <- crawlType pkm
let typeSpec = getType typeString
print typeSpec -- or whatever you wish to do with it.
You can omit the typeString variable by writing†
typeSpec <- getType <$> crawlType pkm
if you prefer; this corresponds to what in a procedural language might look like
var typeSpec = getType(crawlType(pkm));
Alternatively, you can of course include the parsing right in crawlType:
crawlType' :: String -> IO (String, String)
crawlType' pkm = do
src <- openURL url
return . getType . fromBody $ parseTags src
where
fromBody = unwords . drop 6 . take 7 . words . innerText . dropWhile (~/= "<p>")
url = "http://pokemon.wikia.com/wiki/" ++ pkm
†If you're curious what the <$> operator does: this is not built-in syntax like do/<- notation. Instead, it's just an infix version of fmap, which you may better know in its list-specialised version map. Both list [] and IO are functors, which means you can pull them through ordinary functions, changing only the element/outcome values but not the structure of the IO action / list spine.
Related
I am having trouble reading in a level file in Haskell. The goal is to read in a simple txt file with two numbers seperated by a space and then commas. The problem I keep getting is this: Couldn't match type `IO' with `[]'
If I understand correctly the do statement is supposed to pull the String out of the Monad.
readLevelFile :: FilePath -> [FallingRegion]
readLevelFile f = do
fileContent <- readFile f
(map lineToFallingRegion (lines fileContent))
lineToFallingRegion :: String -> FallingRegion
lineToFallingRegion s = map textShapeToFallingShape (splitOn' (==',') s)
textShapeToFallingShape :: String -> FallingShape
textShapeToFallingShape s = FallingShape (read $ head numbers) (read $ head
$ tail numbers)
where numbers = splitOn' (==' ') s
You can't pull things out of IO. You can think of IO as a container (in fact, some interpretations of IO liken it to the box containing Schrödinger's cat). You can't see what's in the container, but if you step into the container, values become visible.
So this should work:
readLevelFile f = do
fileContent <- readFile f
return (map lineToFallingRegion (lines fileContent))
It does not, however, have the type given in the OP. Inside the do block, fileContent is a String value, but the entire block is still inside the IO container.
This means that the return type of the function isn't [FallingRegion], but IO [FallingRegion]. So if you change the type annotation for readLevelFile to
readLevelFile :: FilePath -> IO [FallingRegion]
you should be able to get past the first hurdle.
Let's look at your first function with explicit types:
readLevelFile f = do
(fileContent :: String) <-
(readFile :: String -> IO String) (f :: String) :: IO String
fileContent is indeed of type String but is only available within the execution of the IO Monad under which we are evaluating. Now what?
(map lineToFallingRegion (lines fileContent)) :: [String]
Now you are suddenly using an expression that is not an IO monad but instead is a list value - since lists are also a type of monad the type check tries to unify IO with []. What you actually wanted is to return this value:
return (map lineToFallingRegion (lines fileContent)) :: IO [String]
Now recalling that we can't ever "exit" the IO monad your readLevelFile type must be IO - an honest admission that it interacts with the outside world:
readLevelFile :: FilePath -> IO [FallingRegion]
I am a new to functional programming. I am scraping a website using Scalpel and I need to extract information from links contained in that website. What I can extrapolate though is just part of the link and I need to add to the String "http://www.google.com/" to each of these links. I can't do a normal ++ because I don't have a list of Strings.
Here's the code:
{-# LANGUAGE OverloadedStrings #-}
import Text.HTML.Scalpel
main :: IO ()
main = do
res <- scrapeURL "http://www.whateverLink/" scrapeComic
print res
scrapeComic :: Scraper String [[String]]
scrapeComic =
chroots ("ul" #: ["id" #= "research-teachinglist"]) scrapeLink
scrapeLink :: Scraper String [String]
-- This returns me the parts of the links I want
scrapeLink = (attrs "href" "a")
-- I tried this, but it doesn't work
-- scrapeLink = mapM_ ("http://www.google.com/" ++) (attrs "href" "a")
Any ideas?
Thank you
You have
attrs "href" "a" :: Scraper String [String]
and
("http://www.google.com/" ++) :: String -> String
and you'd like to apply the latter to all the String elements in the former's result list.
First of all, applying it to lists is a matter of
map :: (a -> b) -> [a] -> [b]
so in your case, you have
map ("http://www.google.com/" ++) :: [String] -> [String]
Which gets us one step closer.
Now the next problem is that instead of a pure [String] value, you have a computation Scraper String [String]. However, Scraper str is an instance of Functor for any choice of str, in particular, for str ~ String, i.e. Scraper String is a Functor.
What that gives us is a way of applying pure functions to Scraper Strings:
fmap :: (a -> b) -> Scraper String a -> Scraper b
in particular, we have
fmap (map ("http://www.google.com/" ++)) :: Scraper String [String]
leading to
scrapeLink :: Scraper String [String]
scrapeLink = fmap (map ("http://www.google.com/" ++)) (attrs "href" "a")
I have revisited Haskell lateley and constructed a toy programming language parser/interpreter. Using Parsec for lexing and parsing and a separate interpreter. I'm running in to some issues with feeding the result from the parser to my interpreter and handle the potential error from both the interpreter and parser. I end up with something like this:
main = do
fname <- getArgs
input <- readFile (head fname)
case lparse (head fname) input of
Left msg -> putStrLn $ show msg
Right p -> case intrp p of
Left msg -> putStrLn $ show msg
Right r -> putStrLn $ show r
This dosn't look pretty at all. My problem is that lparse returns Either ParseError [(String, Stmt)] and itrp returns the type Either ItrpError Stmt so I'm having a real hard time feeding the Right result from the parser to the interpreter and at the same time bail and print the possible ParseError or IntrpError.
The closest to what i want is something like this
main = do
fname <- getArgs
input <- readFile (head fname)
let prog = lparse (head fname) input
(putStrLn . show) (intrp <$> prog)
But this will not surprisingly yield a nested Either and not print pretty either.
So are there any nice Haskell ideomatic way of doing this threading results from one computation to another and handling errors (Lefts) in a nice way without nesting cases?
Edit
adding types of lparse and itrp
lparse :: Text.Parsec.Pos.SourceName -> String -> Either Text.Parsec.Error.ParseError [([Char], Stmt)]
intrp :: [([Char], Stmt)] -> Either IntrpError Stmt
While not perfect, I'd create a helper function for embedding any Showable error from Either into MonadError:
{-# LANGUAGE FlexibleContexts #-}
import Control.Monad.Except
strErr :: (MonadError String m, Show e) => Either e a -> m a
strErr = either (throwError . show) return
Then if you have a computation that can fail with errors, like
someFn :: ExceptT String IO ()
someFn = strErr (Left 42)
you can run it (printing errors to stdout) as
main :: IO ()
main = runExceptT someFn >>= either putStrLn return
In your case it'd be something like
main = either putStrLn return <=< runExceptT $ do
fname <- liftIO getArgs
input <- liftIO $ readFile (head fname)
prog <- strErr $ lparse (head fname) input
r <- strErr $ interp prog
print r
Well, if you want to chain successful computations, you can always use >>= to do that. For instance in your case:
lparse (head fname) input >>= intrp
And if you want to print out either your error message you can use the either class that takes two handler functions, one for the case when you have Left a (error in your case) and another for Right b (in your case a successful thing). An example:
either (putStrLn . show) (putStrLn . show) (lparse (head fname) input >>= intrp)
And if anything fails in your chain (any step of your monadic chain becomes Left a) it stops and can for instance print out the error message in the above case.
Given this little project I'm using to learn Haskell, I would like to move my request handler's code generation to a Hamlet template, but am unsure how to pass things around.
My current code generates the following error when lines are uncommented, which is the first blocker:
Couldn't match expected type `String -> String'
with actual type `String'
In the return type of a call of `renderHtml'
Probable cause: `renderHtml' is applied to too many arguments
In the expression: renderHtml ($ (shamletFile "fileList.hamlet"))
In an equation for `myTemplate':
myTemplate = renderHtml ($ (shamletFile "fileList.hamlet"))
Code:
site :: Snap ()
site =
ifTop (writeBS "hello world") <|>
route [ ("foo", writeBS "ba"),
("view_root_json_files", listRootFilesHandler)
] <|>
dir "static" (serveDirectory ".")
--myTemplate :: String -> String
--myTemplate = renderHtml ( $(shamletFile "fileList.hamlet") )
toText :: [FilePath] -> Text
toText = foldMap (flip snoc '\n' . pack)
listRootFilesHandler :: Snap ()
listRootFilesHandler = do
filenames <- liftIO $ getDirectoryContents "data"
let filtered_filenames = filter (not . isPrefixOf ".") filenames
writeText $ toText filtered_filenames
Ghc is telling you the correct type signature to put there. Just replace String -> String with String.
Yesterday i tried to write a simple rss downloader in Haskell wtih hte help of the Network.HTTP and Feed libraries. I want to download the link from the rss item and name the downloaded file after the title of the item.
Here is my short code:
import Control.Monad
import Control.Applicative
import Network.HTTP
import Text.Feed.Import
import Text.Feed.Query
import Text.Feed.Types
import Data.Maybe
import qualified Data.ByteString as B
import Network.URI (parseURI, uriToString)
getTitleAndUrl :: Item -> (Maybe String, Maybe String)
getTitleAndUrl item = (getItemTitle item, getItemLink item)
downloadUri :: (String,String) -> IO ()
downloadUri (title,link) = do
file <- get link
B.writeFile title file
where
get url = let uri = case parseURI url of
Nothing -> error $ "invalid uri" ++ url
Just u -> u in
simpleHTTP (defaultGETRequest_ uri) >>= getResponseBody
getTuples :: IO (Maybe [(Maybe String, Maybe String)])
getTuples = fmap (map getTitleAndUrl) <$> fmap (feedItems) <$> parseFeedString <$> (simpleHTTP (getRequest "http://index.hu/24ora/rss/") >>= getResponseBody)
I reached a state where i got a list which contains tuples, which contains name and the corresponding link. And i have a downloadUri function which properly downloads the given link to a file which has the name of the rss item title.
I already tried to modify downloadUri to work on (Maybe String,Maybe String) with fmap- ing on get and writeFile but failed with it horribly.
How can i apply my downloadUri function to the result of the getTuples function. I want to implement the following main function
main :: IO ()
main = some magic incantation donwloadUri more incantation getTuples
The character encoding of the result of getItemTitle broken, it puts code points in the places of the accented characters. The feed is utf8 encoded, and i thought that all haskell string manipulation functions are defaulted to utf8. How can i fix this?
Edit:
Thanks for you help, i implemented successfully my main and helper functions. Here comes the code:
downloadUri :: (Maybe String,Maybe String) -> IO ()
downloadUri (Just title,Just link) = do
item <- get link
B.writeFile title item
where
get url = let uri = case parseURI url of
Nothing -> error $ "invalid uri" ++ url
Just u -> u in
simpleHTTP (defaultGETRequest_ uri) >>= getResponseBody
downloadUri _ = print "Somewhere something went Nothing"
getTuples :: IO (Maybe [(Maybe String, Maybe String)])
getTuples = fmap (map getTitleAndUrl) <$> fmap (feedItems) <$> parseFeedString <$> decodeString <$> (simpleHTTP (getRequest "http://index.hu/24ora/rss/") >>= getResponseBody)
downloadAllItems :: Maybe [(Maybe String, Maybe String)] -> IO ()
downloadAllItems (Just feedlist) = mapM_ downloadUri $ feedlist
downloadAllItems _ = error "feed does not get parsed"
main = getTuples >>= downloadAllItems
The character encoding issue has been partially solved, i put decodeString before the feed parsing, so the files get named properly. But if i want to print it out, the issue still happens. Minimal working example:
main = getTuples
It sounds like it's the Maybes that are giving you trouble. There are many ways to deal with Maybe values, and some useful library functions like fromMaybe and fromJust. However, the simplest way is to do pattern matching on the Maybe value. We can tweak your downloadUri function to work with the Maybe values. Here's an example:
downloadUri :: (Maybe String, Maybe String) -> IO ()
downloadUri (Just title, Just link) = do
file <- get link
B.writeFile title file
where
get url = let uri = case parseURI url of
Nothing -> error $ "invalid uri" ++ url
Just u -> u in
simpleHTTP (defaultGETRequest_ uri) >>= getResponseBody
downloadUri _ = error "One of my parameters was Nothing".
Or maybe you can let the title default to blank, in which case you could insert this just before the last line in the previous example:
downloadUri (Nothing, Just link) = downloadUri (Just "", Just link)
Now the only Maybe you need to work with is the outer one, applied to the array of tuples. Again, we can pattern match. It might be clearest to write a helper function like this:
downloadAllItems (Just ts) = ??? -- hint: try a `mapM`
downloadAllItems Nothing = ??? -- don't do anything, or report an error, or...
As for your encoding issue, my guesses are:
You're reading the information from a file that isn't UTF-8 encoded, or your system doesn't realise that it's UTF-8 encoded.
You are reading the information correctly, but it gets messed up when you output it.
In order to help you with this problem, I need to see a full code example, which shows how you're reading the information and how you output it.
Your main could be something like the shown below. There may be some more concise way to compose these two operations though:
main :: IO ()
main = getTuples >>= process
where
process (Just lst) = foldl (\s v -> do {t <- s; download v}) (return ()) lst
process Nothing = return ()
download (Just t, Just l) = downloadUri (t,l)
download _ = return ()