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:
- How to compare if two strings are equal? Is what I'm doing correct?
- 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 if
s 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