haskell · release · env-extra

env-extra v1.0.0.0

Introducing env-extra, a small Haskell library that improves environment variable handling with Text support, flexible return types, and better error handling—reducing boilerplate from System.Environment.

It's hard (though possible) to imagine a script that doesn't access environment variables. In the Haskell ecosystem, there's a good built-in module called System.Environment that does the job, but it has several drawbacks. First, it uses String as both input and output. Second, it lives in IO. Whilst these drawbacks aren't critical, calling all these Text.pack, Text.unpack, and liftIO functions in every programme drives me nuts. So several years ago (around 2016), I decided to write a simple library to wrap it for me.

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

In a 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 is the most interesting part here. I'll 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 is that envMaybe is polymorphic in its return type, which means it plays nicely in composition chains and you don't need to explicitly convert between Text, String, and 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, ideas, and thoughts! Contributions are always welcome!