Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell design: Struggling with IO

Tags:

haskell

I am a novice haskell programmer and I am trying to write some Haskell cgi which will read from a MySQL DB and output JSON. I am able to generate the right JSON but am unable to get the data types correctly to be able to output JSON correctly. I also think that I am primarily thinking imperative still. Here is my code. Note that getTopBrands provides json output.

My problem is that I am unable to figure out how to return "[Char]" from getTopBrands and not "IO [Char]". It looks to me I am still thinking imperative. Any pointers, suggestions to fix this would be greatly appreciated. Please let me know if I need to provide the rest of the code.

RODB.hs:

{-# LANGUAGE RecordWildCards, OverloadedStrings, PackageImports #-}

module Main where

import RODB
import ROOutput
import System.Environment
import Database.HDBC
import Network.Socket(withSocketsDo)
import Network.CGI
import Text.XHtml
import qualified "bytestring" Data.ByteString.Lazy.Char8 as LBS
import Data.Aeson

page :: Html
page = body << h1 << str

main = runCGI $ handleErrors cgiMain

cgiMain :: CGI CGIResult
cgiMain =
    do out <- getTopBrands 10 1
       setHeader "Content-type" "application/json"
       output $ renderHtml page out

getTopBrands :: Integer -> Integer -> IO [Char]
getTopBrands limit sorted =
    do let temp = 0
       dbh <- connect "127.0.0.1" "ReachOutPublicData" "root" "admin" "/tmp/mysql.sock"
       if sorted == 1
       then do brandlist <- getBrands dbh limit True
               json <- convPublicBrandEntrytoJSON brandlist
               return $ LBS.unpack json
       else do brandlist <- getBrands dbh limit False
               json <- convPublicBrandEntrytoJSON brandlist
               return $ LBS.unpack json
like image 291
Animesh Avatar asked Feb 17 '23 20:02

Animesh


1 Answers

As Niklas B said, getTopBrands being in IO is right, since it depends on I/O. I guess your problem is that you get a type error from that when you try to use it directly,

cgiMain :: CGI CGIResult
cgiMain =
    do out <- getTopBrands 10 1
       setHeader "Content-type" "application/json"
       output $ renderHtml page out

since all statements in a do-block must belong to the same monad, and the rest of the block is in CGI. But, CGI is a MonadIO, thus you can simply liftIO it into CGI,

cgiMain :: CGI CGIResult
cgiMain =
    do out <- liftIO $ getTopBrands 10 1
       setHeader "Content-type" "application/json"
       output $ renderHtml page out

The next point Niklas raised is also right, the second Integer argument of getTopBrands should really be a Bool. However, even with its current type, the code duplication is entirely unnecessary, the difference between the two branches is just the Bool argument to getBrands, so

getTopBrands :: Integer -> Integer -> IO [Char]
getTopBrands limit sorted =
    do let temp = 0
       dbh <- connect "127.0.0.1" "ReachOutPublicData" "root" "admin" "/tmp/mysql.sock"
       brandlist <- getBrands dbh limit (sorted == 1)
       json <- convPublicBrandEntrytoJSON brandlist
       return $ LBS.unpack json

just pass it the condition on which you branched.

Niklas' third point

I also don't see why convPublicBrandEntrytoJSON would need to live in IO, but since you didn't provide its definition I cannot suggest an improvement here.

also looks very valid, a conversion would usually be a pure function. If the only reason why it is in IO is the ability to write

json <- convPublicBrandEntrytoJSON brandlist

you should be aware that you can bind results of pure functions in a do-block

let json = convPublicBrandEntrytoJSON brandlist

using let.

like image 77
Daniel Fischer Avatar answered Feb 22 '23 22:02

Daniel Fischer