DEV Community

mtwtkman
mtwtkman

Posted on

Haskell I/O Handler is my friend now

I'm learning Haskell and it feeds me some new experiences, but I'm scary against its difficulties sometimes. (Actually so many times.)

At a glance, Haskell's I/O handler looks difficult but not actually.

I figured out that Haskell's I/O handler is so simple and there is no difference with other language's ones like python's file like object.

Let's go with a concrete example.

I wrote logging function for my interest like below.(Please ignore detail and structures, naming.)

module Main where

import Data.Time (UTCTime, getCurrentTime)
import Prelude hiding (log)
import System.IO (stdout, Handle, openFile, IOMode (WriteMode), hFlush, hClose, hPutStrLn)

data Severity
  = Debug
  | Info
  | Warning
  | Error
  deriving (Ord, Eq)

severityLabel :: Severity -> Char
severityLabel Debug = 'D'
severityLabel Info = 'I'
severityLabel Warning = 'W'
severityLabel Error = 'E'

newtype LogFormatter = LogFormatter {format :: UTCTime -> Severity -> String -> String}

data Logger = Logger
  { loggerSeverity :: Severity,
    loggerHandler :: Handle,
    loggerFormatter :: LogFormatter
  }

log :: String -> Logger -> IO ()
log msg logger = do
  timestamp <- getCurrentTime
  let formatter = loggerFormatter logger
      severity = loggerSeverity logger
      handler = loggerHandler logger
  hPutStrLn handler (format formatter timestamp severity msg)
  hFlush handler

basicFormatter :: LogFormatter
basicFormatter = LogFormatter formatter
  where
    formatter t s msg = show t <> " - " <> [severityLabel s] <> ": " <> msg

stdInLogger :: Severity -> Logger
stdInLogger sev = Logger sev stdout basicFormatter

main :: IO ()
main = do
  let debugLogger = stdInLogger Debug
  log "this is logger" debugLogger

  let logfilePath = "out.log"
  hdl <- openFile logfilePath WriteMode
  let fileLogger = Logger Debug hdl basicFormatter
  log "this is on file" fileLogger
  hClose hdl
Enter fullscreen mode Exit fullscreen mode

This code generates like 2023-05-07 02:27:25.997054484 UTC - D: <arbitrary message> to stdout and a logfile.

When I am going to display some messages to stdout, I use print and putStrLn usually.

As well as I am goint to write out some messages to any files, I use openFile and writeFile usually.

In above example case, I have to make the something to write out to real world via I/O handler abstraction, so I guess my function log is the one of core concept of Haskell's I/O handleing.

log msg logger = do
  timestamp <- getCurrentTime
  let formatter = loggerFormatter logger
      severity = loggerSeverity logger
      handler = loggerHandler logger
  hPutStrLn handler (format formatter timestamp severity msg)
  hFlush handler
Enter fullscreen mode Exit fullscreen mode

log uses h prefixed function for System.IO.Handle. So this is
all things about Haskell's I/O handling.

I just do passing System.IO.Handle type something to the I/O handle wrapper and this wrapper just writes some messages to handler's buffer and finally flushes, NO DIFFICULTIES! YAY!

Haskell is difficult to me. This truth is maybe never changed forever to me.

But I learned those difficurlties are from some impressions.

Forgetting type system, Monad, etc and treating as a just function makes me more easy to understanding what is it.

Now Haskell's I/O handling is no fear, it is my friend (maybe).

Top comments (1)

Collapse
 
zelenya profile image
Zelenya

That's a good approach – to focus on functions and not worry about the rest. Thank you for sharing