0
votes

I am having a type issue with Haskell, the program below throws the compile time error:

Couldn't match expected type ‘bytestring-0.10.8.2:Data.ByteString.Lazy.Internal.ByteString’ with actual type ‘Text’

Program is:

{-# LANGUAGE OverloadedStrings #-}
module Main where

...

import Control.Concurrent (MVar, newMVar, modifyMVar_, modifyMVar, readMVar)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Network.WebSockets as WS
import Data.Map (Map)
import Data.Aeson (decode)

...

application :: MVar ServerState -> WS.ServerApp
application state pending = do
    conn <- WS.acceptRequest pending
    msg <- WS.receiveData conn
    -- EITHER this line can be included
    T.putStrLn msg
    -- OR these two lines, but not both
    decodedObject <- return (decode msg :: Maybe (Map String Int))
    print decodedObject

...

It seems to me that the basic issue is that putStrLn expects Text whereas decode expects Bytetring.

What I don't get is why I can run this section of the code:

T.putStrLn msg

Or I can run this section of the code:

decodedObject <- return (decode msg :: Maybe (Map String Int))
print decodedObject

But not both together.

What is the proper way to resolve this issue in the program?

I guess this is something like Type Coercion, or Type Inference, or what would be Casting in other languages. The problem is I don't know how to phrase the problem clearly enough to look it up.

It's as if msg can be one of a number of Types, but as soon as it is forced to be one Type, it can't then be another...

I'm also not sure if this overlaps with Overloaded strings. I have the pragma and am compiling with -XOverloadedStrings

I'm quite a newbie, so hope this is a reasonable question.

Any advice gratefully received! Thanks

2
Nothing is casted here, the type of WS.receiveData conn is polymorphic in its return type. The equivalent in Java would be a generic method and in C++ it'd be a template. The difference is that neither Java nor C++ can infer type arguments that are only used as the return type, so in Java or C++ you'd have to explicitly write receiveData<Text>(conn) to get a Text or receiveData<ByteString>(conn) to get a ByteString. Haskell is smart enough to infer this based on what you do with the result, but if you use it as both a ByteString and a Text, that no longer works.sepp2k
A ByteString is a sequence of bytes, while a Text is a sequence of characters. You can turn a ByteString into a Text only if you know that the bytes are indeed text, and which encoding is being used. For instance, you can use decodeUtf8 :: ByteString -> Text from Data.Text.Encodingchi
Thanks @sepp2k, @ chi, thats really helpful, think I understand it all now, and summed it up in my extra answerrjmurt

2 Answers

3
votes

This is because WS.receiveData is polymorphic on its return type:

receiveData :: WebSocketsData a => Connection -> IO a

it only needs to be WebSocketsData a instance, which both Text and ByteString are. So the compiler just infers the type.

I suggest you just assume it's a ByteString, and convert in Text upon the putStrLn usage.

0
votes

Thanks to everyone for their advice. My final understanding is that any value in Haskell can be polymorphic until you force it to settle on a type, at which point it can't be any other type (stupid, but I hadn't seen a clear example of that before).

In my example, WS.receiveData returns polymorphic IO a where a is an instance of class WebsocketData which itself is parameterised by a type which can be either Text or Bytestring.

Aeson decode expects a (lazy) Bytestring. Assuming that we settle on (lazy) Bytestring for our a, this means the first line that I mentioned before needs to become:

T.putStrLn $ toStrict $ decodeUtf8 msg

to convert the lazy ByteString to a strict Text. I can do this so long as I know the incoming websocket message is UTF8 encoded.

I may have got some wording wrong there, but think that's basically it.