env-extra v1.0.0.0

Posted on January 6, 2020
Updated on July 20, 2022
Tagged as #haskell, #release, #env-exta

It’s hard (though possible) to imagine a script that doesn’t access environment variables. In Haskell ecosystem there is a good built-in module System.Environment that does the job, but it has several drawbacks. First of all, it uses String data type as input and as an output. And secondly, it lives in IO. Both of these drawbacks are not that critical, but calling all these Text.pack, Text.unpack and liftIO in every program drives me nuts. So several years ago (somewhere in 2016) I decided to write a simple library that wraps it for me.

I’ve been using this library for a long time and today I uploaded it to Hackage. While it’s really small, I still think that some of you might find it useful, because it has nice features that original System.Environment doesn’t have. Let’s take a look!

So in the nutshell, there are three functions to access environment variables:

  • envMaybe of type ( MonadIO m, IsString a ) => Text -> m (Maybe a) - a wrapper for lookupEnv. Aside from working with Text input it also lives in MonadIO and returns an IsString, which the most interesting part here. I will talk about it shortly.
  • getEnv of type ( MonadThrow m, MonadIO m, IsString a ) => Text -> m a - the unsafe version of envMaybe.
  • envRead of type ( MonadIO m ) => Reader a -> Text -> m (Maybe a) - a helper function that also reads (or parses) a value from environment variable by specifying the Reader.

In most cases my choice is between envMaybe and getEnv depending on my desire to handle missing variable.

> envMaybe "NAME"
Nothing

> setEnv "NAME" "Boris"

> envMaybe "NAME"
Just "Boris"

What’s interesting, envMaybe is polymorphic in it’s return type, which means that it plays nicely in composition chains and you don’t need to explicitly convert between Text, String, ByteString data types.

> :t putStrLn
putStrLn :: String -> IO ()

> getEnv "NAME" >>= putStrLn
Boris

> setEnv "SOME_VAR" "NAME"

> getEnv "SOME_VAR"
"NAME"

> getEnv "SOME_VAR" >>= getEnv
"Boris"

The other function that I use (though not that often) is envRead.

> setEnv "AGE" "10"

> envRead decimal "AGE"
Just 10

> envRead hexadecimal "AGE"
Just 16

> envRead hexadecimal "HOME"
Nothing

In some rare cases you might want to use the Read instance for parsing. Though it’s not advisable.

> data Status = SomeStatus1 | SomeStatus2 deriving (Show, Read, Eq)

> getEnv "STATUS"
"SomeStatus2"

> envRead read "STATUS" :: IO (Maybe Status)
Just SomeStatus2

I hope you’ll find this library helpful. Please do send me your comments, idea and thoughts! And any contribution is welcome!