Home > Software design >  How to return value in a do block in Haskell?
How to return value in a do block in Haskell?

Time:08-10

I just started learning Haskell recently, and it's a really cool language, but I'm confused about how I should "return" value in a function once I call a do block.

Specifically, consider the following function parseMessage,

parseMessage :: String -> LogMessage
parseMessage string = do
    let brokenLine = words string
    let label = take 1 brokenLine
    if label == "E" then Error
    else if label == "I" then Info
    else if label == "W" then Warning
    else return Unknown

where Error, Info, and Warning are three constructors of the type LogMessage. I think there are two problems with the code - both of which I don't know how to fix:

  1. How to compare if two strings are equal? Is what I'm doing correct?
  2. How to "return" Error, Info, Warning in the if-else statement?

Also, I know that I can usually use "guards" like this:

parseMessage :: String -> LogMessage
parseMessage string 
   | label == "E" = Error
   | label == "I" = Info
   | label == "W" = Warning
   | otherwise = Unknown

but where should I process

let brokenLine = words string
let label = take 1 brokenLine

Sorry if this is something really basic! Thanks in advanc3 :))

CodePudding user response:

do blocks are not for general computation. They're for doing monadic computation. In fact, my recommendation to beginners is to treat do as a special IO thing and only use it for IO computation. Once you're more comfortable with monads, you can extend that to other monadic contexts.

Similarly, the word return is not a return statement like it is in other languages. It's actually a terribly-named synonym for the pure function, which wraps a value in a minimal applicative (or, in return's case, monadic) context.

But you're not doing IO here. You're not doing anything monadic at all. You're doing an ordinary pure computation. So you don't need do.

parseMessage :: String -> LogMessage
parseMessage string =
    let brokenLine = words string
        label = take 1 brokenLine in
    if label == "E" then Error
    else if label == "I" then Info
    else if label == "W" then Warning
    else Unknown

and we can get rid of those else ifs with a pattern match.

parseMessage :: String -> LogMessage
parseMessage string =
    let brokenLine = words string in
    case take 1 brokenLine of
      "E" -> Error
      "I" -> Info
      "W" -> Warning
      _ -> Unknown

Finally, string is a String, so brokenLine (after being tokenized) is a [String]. take takes a prefix, i.e. another [String], so I imagine what you want is head, which returns the first element, not as a list.

parseMessage :: String -> LogMessage
parseMessage string =
    let brokenLine = words string in
    case head brokenLine of
      "E" -> Error
      "I" -> Info
      "W" -> Warning
      _ -> Unknown

and if you want to be prepared to handle the case of the empty list as well, you can pattern match directly on the list

parseMessage :: String -> LogMessage
parseMessage string =
    let brokenLine = words string in
    case brokenLine of
      ("E":_) -> Error
      ("I":_) -> Info
      ("W":_) -> Warning
      _ -> Unknown
  • Related