From OO to FP: Haskell I/O, Part 2
In my Part-1
post on this topic, we actually did all the I/O I'm going to do here.
We lazily read in the entire sample data file, a file containing data
describing events generated by a process monitor. My next goal was to
re-hydrate my Events from the Strings serialized to the file. These Strings were generated by calling the function show on my List of Events.
Time to back up a little. We got show for free, but we had to ask for it. Leaving aside the Event data type for a moment and looking at the simpler Property data type, remember how we defined it:
data Property = Property {
key :: Key,
value:: Value }
deriving (Show)At that time we discussed "deriving (Show)" by simply noting it allowed ghci to know how to output Property
values. Of course, there's more to it than that. The "more" is
Haskell typeclasses. A Haskell typeclass defines a set of functions
that can be defined to operate on a data type. The show function is a member of the Show typeclass and has the following type:*EventProcessor> :type show show :: Show a => a -> StringFor a moment, let's see what had happened if we had not specified that Property derives Show:
-- file: c:/HaskellDev/eventProcessor/simpleProperty.hs
type Key = String
type Value = String
data Property = Property {
key :: Key,
value:: Value }If we load this file, then create a Property, then try to show it, we see the following: Prelude> :load simpleProperty.hs
[1 of 1] Compiling Main ( simpleProperty.hs, interpreted )
Ok, modules loaded: Main.
*Main> let prop1 = Property "key1" "value1"
*Main> show prop1
:1:1:
No instance for (Show Property)
arising from a use of `show'
Possible fix: add an instance declaration for (Show Property)
In the expression: show prop1
In an equation for `it': it = show prop1That's considerably more helpful than a typical compiler error message. The key is the hint to add an instance declaration. In order for the functions of a typeclass to be applicable to your data type, you need to declare an instance of that typeclass that handles your data type. In our case, we can get Show simply by stating that we derive it. So we go back to our old definition:
-- file: c:/HaskellDev/eventProcessor/simpleProperty.hs
type Key = String
type Value = String
data Property = Property {
key :: Key,
value:: Value }
deriving (Show)Repeating our earlier test, now we get the following: *Main> :load simpleProperty.hs
[1 of 1] Compiling Main ( simpleProperty.hs, interpreted )
Ok, modules loaded: Main.
*Main> let prop1 = Property "key1" "value1"
*Main> show prop1
"Property {key = \"key1\", value = \"value1\"}"That's definitely an improvement. But, back to the subject -- what I want is to be able to read a String and turn it in to a Property. First, let's try to read a String into an Integer:
*Main> read "5" :1:1: Ambiguous type variable `a0' in the constraint: Read a0) arising from a use of `read' Probable fix: add a type signature that fixes these type variable(s) In the expression: read "5" In an equation for `it': it = read "5"
Again, a helpful hint from ghci. It basically doesn't know what we are trying to create and suggests adding a type signature. Here's how you would do it:
*Main> let anInt = (read "5")::Integer *Main> :type anInt anInt :: Integer *Main> show anInt "5"
Haskell now knows we're trying to read an Integer, so it recognizes that anInt is an Integer and knows how to show it. All of this should tell you that Integer has defined instances for both Show and Read.
Can we get Read "for free" simply by stating that our data type derives it? It can't hurt to try:
-- file: c:/HaskellDev/eventProcessor/simpleProperty.hs
type Key = String
type Value = String
data Property = Property {
key :: Key,
value:: Value }
deriving (Read, Show)Next, I'll create a Property and output it with show, then see if that format (which, incidentally, is how I created my sample Event file -- using show on a List of Events) is "read"-able: *Main> let prop1 = Property "key1" "value1"
*Main> show prop1
"Property {key = \"key1\", value = \"value1\"}"
*Main> let prop2 = (read "Property {key = \"key1\", value = \"value1\"}")::Property
*Main> show prop2
"Property {key = \"key1\", value = \"value1\"}"This
is great news -- I didn't have to write a parser, and I'm perfectly
happy to use, as my format, the same format that Haskell uses to show a Haskell data structure.What I would really like to do is to be able to read a full Event, as defined in my earlier posts, including a List of Events. Here's an example Event:
"Event {timestamp = 1320512200548, className = \"java.lang.String\", lineNumber = 1293,
message = \"NPE in substring()\", properties = [Property {key = \"userId\", value = \"smith\"},
Property {key = \"sessionId\", value = \"ABCD1234\"}]}"Let's try the same trick with Event that we used with Property:
-- file: c:/HaskellDev/eventProcessor/notSoSimpleProperty.hs
type Timestamp = Integer
type ClassName = String
type LineNumber = Integer
type Message = String
type Key = String
type Value = String
type Properties = [Property]
data Property = Property {
key :: Key,
value:: Value }
deriving (Read, Show)
data Event = Event {
timestamp :: Timestamp,
className :: ClassName,
lineNumber :: LineNumber,
message :: Message,
properties :: Properties }
deriving (Read, Show)Was it enough simply to declare that Event derives Show? Let's see: *EventProcessor> :load notSoSimpleProperty.hs
[1 of 1] Compiling Main ( notSoSimpleProperty.hs, interpreted )
Ok, modules loaded: Main.
*Main> let event1 = (read "Event {timestamp = 1320512200548, className = \"java.lang.String\",
lineNumber = 1293, message = \"NPE in substring()\", properties = [Property {key = \"userId\", value = \"smith\"},
Property {key = \"sessionId\", value = \"ABCD1234\"}]}")::Event
*Main> show event1
"Event {timestamp = 1320512200548, className = \"java.lang.String\", lineNumber = 1293, message = \"NPE in substring()\",
properties = [Property {key = \"userId\", value = \"smith\"},Property {key = \"sessionId\", value = \"ABCD1234\"}]}"
*Main> show (properties event1)
"[Property {key = \"userId\", value = \"smith\"},Property {key = \"sessionId\", value = \"ABCD1234\"}]"This is great. We can take a String representation of an Event, as output by show, and use it directly with read to instantiate an Event variable.This discussion in no way described how to provide an instance of a typeclass; maybe in another post. Right now I still want to create Events from my sample Event file.
My first cut at this is the following:
import System.IO
import EventProcessor
main :: IO ()
main = do
inh <- openFile "sampleEvents" ReadMode
inContents <- hGetContents inh
let eventList = processData (lines inContents)
hClose inh
processData :: [String] -> [Event]
processData = map createEvent
createEvent :: String -> Event
createEvent event = (read event)::EventWhile this appears to be correct, it doesn't do anything that proves I've been able to parse my example-file lines into a List of Events. Unfortunately, at this point I'm a little stuck, as I haven't yet figured out how to correctly extract elements from my Events (I can do so interactively in ghci, but I am plagued by compiler errors if I try to do so in the do block).
I'm going to leave this for a while and go back to some online resources/tutorials, then revisit. The problem for me is that I still don't understand the I/O monad, as it's called. It's becoming clear to me that the concept of a monad -- apparently so integral to Haskell -- isn't easily grasped, as evidenced by the overwhelming number of "Here's my take on monads" tutorials on-line (one author, I see, jokes that everyone who learns about monads appears to post a tutorial on the subject shortly thereafter). So I'm off to the Haskell Wiki to learn about monads, then learn some more about Haskell I/O, and then pick up where I left off.
From http://wayne-adams.blogspot.com/2011/12/from-oo-to-fp-haskell-io-part-2.html
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)





