user1156995
user1156995

Reputation:

Haskell Snap Framework - Type mismatch ByteString / Maybe ByteString

I am fairly new to Haskell and Snap and I am working through my own project for a small web forum using snap. The problem I am having is in understanding the conflict and how to resolve for the following code.

handleCategoryAdd :: H ()
handleCategoryAdd = method POST (withLoggedInUser go)
  where
    go user = do
      bs <- getParam "categoryName"
      cN <- B.unpack $ Just bs
      cD <- getParam "categoryDesc"
      cT <- getCurrentTime
      return (Db.saveCategory (Db.Category 1 cN cT "1" ))
      redirect "/"

Gives me a type mismatch error as follows:

src\Site.hs:118:22:
    Couldn't match expected type `ByteString'
                with actual type `Maybe ByteString'
    In the first argument of `unpack', namely `bs'
    In a stmt of a 'do' block: cN <- unpack bs
    In the expression:
      do { bs <- getParam "categoryName";
           cN <- unpack bs;
           cD <- getParam "categoryDesc";
           cT <- getCurrentTime;
           .... }

Any advice to clear up the confusion would be much appreciated, I have been searching for some way to unwrap the maybe and just retrieve the bytestring but have been unsuccessful.

Many thanks!

Upvotes: 0

Views: 334

Answers (2)

Levi Pearson
Levi Pearson

Reputation: 4984

A nice way of extracting a value from a Maybe a type is to use the fromMaybe function from the Data.Maybe module. It's useful if you have a default value to use in the case of a Nothing value in the Maybe.

I will present an example in the IO monad rather than some Snap monad, in order to avoid pulling in the Snap libraries for the demo code to work, but it should work the same (aside from type signature changes) as long as the handler is in the same monad as the getParam actions.

{-# LANGUAGE OverloadedStrings #-}
import           Data.Maybe
import           Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B

getParam1, getParam2 :: IO (Maybe ByteString)
getParam1 = return $ Just "Hello"
getParam2 = return $ Nothing

main :: IO ()
main = do
   str1 <- getParam1 >>= return . fromMaybe "No value"
   str2 <- getParam2 >>= return . fromMaybe "No value"

   B.putStrLn str1
   B.putStrLn str2

Since getParam1 and getParam2 are IO (Maybe ByteString), we know we need to use a monadic action to get at the Maybe ByteString value inside.

By looking at the type signature of >>=, which is m a -> (a -> m b) -> m b, we can set type a to be Maybe ByteString and type b to be IO ByteString, which means we are looking to apply the bind with these specific types:

-- (Type of getParam1 >>= Type of our function to supply)    -> Desired type 
IO (Maybe ByteString) -> (Maybe ByteString -> IO ByteString) -> IO ByteString

With that in mind, we can look at the type of fromMaybe:

fromMaybe :: a -> Maybe a -> a

We can partially apply fromMaybe with a default ByteString and get:

fromMaybe "No value" :: Maybe ByteString -> ByteString

Now we compose that function with return to put the ByteString back in IO:

return . fromMaybe "No value" :: Maybe ByteString -> IO ByteString

This is the type we need to use >>= on the result of getParam1.

Since we are in a do block in the IO monad, we can extract the ByteString for further use using the <- binding arrow syntax:

str1 <- getParam1 >>= return . fromMaybe "No value"

At this point, you have a pure ByteString in str1 to use as you see fit in the do block.

Hopefully you find this explanation helpful!

Upvotes: 1

DiegoNolan
DiegoNolan

Reputation: 3766

Just is a constructer for the type Maybe a. getParam, i'm pretty sure, returns Snap Maybe Bytestring so bs is of type Maybe Bytestring. When you say Just bs you then would have Maybe (Maybe Bytestring) which is clearly what you don't want. You are looking for fromJust :: Maybe a -> a but this function is dangerous because you haven't checked if getParam has even succeeded and there is an input on on the html page with that name.

What you should do is use cases or >>=.

Something like

bs <- getParam "categoryName"
case bs of
   Nothing -> writeBS "failed"
   Just b  -> do
      let unpacked = B.unpack b
      -- ... Do more stuff

EDIT

It depends what you are trying to do. Bind can be useful and so can liftM, lifM2, liftM3, etc.

In your case you may want liftM2. I don't know what your types are but I'll give a very simple example. Save you have have those 2 Bytestrings you could do something like this.

bs1 <- getParam "one"
bs2 <- getParam "two"

case (liftM2 append bs1 bs2) of
   Nothing -> writeBS "Failed"
   Just b  -> writeBS b

Upvotes: 2

Related Questions