Proposal: System.Environment.getEnv should return Maybe

It's awkward and surprising how getEnv throws an exception when an env var is not set, since it's very common for env vars to be optional, and it's not convenient to catch exceptions in haskell (I have to look up Control.Exception every time, and then add LANGUAGE ScopedTypeVariables). I wind up using 'fmap lookup k Environment.getEnvironment' instead. So I propose a function: lookupEnv :: String -> IO (Maybe String) Then getEnv can be deprecated and eventually removed. Or maybe not if people want to argue the current getEnv is useful and deprecation warnings would be too annoying. Yeah, it's a trivial thing, but it's nice to sand off some of these rough edges. Discussion: 2 weeks, March 7

On Wed, Feb 22, 2012 at 18:05, Evan Laforge
So I propose a function: lookupEnv :: String -> IO (Maybe String)
+1 Maybe if exceptions fit the ecosystem a bit better, but as is getEnv does not make enormous amounts of sense to me (it's a bit like assuming the behavior of the C version on a nonexistent variable should be a core dump; that's taking the nature of unchecked pointers just a bit too literally...). -- brandon s allbery allbery.b@gmail.com wandering unix systems administrator (available) (412) 475-9364 vm/sms

Another thing that jumped out at me when I looked at the source was that it decodes based on getFileSystemEncoding. Given all the brouhaha about FilePaths, maybe I can attach a rider to add a 'lookupEnvBytes :: ByteString -> IO (Maybe ByteString)' and 'getEnvironmentBytes :: IO [(ByteString, ByteString)]', since I'm pretty sure there's no defined encoding for env vars on unix. I hesitate to suggest it because I don't have an immediate use for it myself, but given that we decided we wanted RawFilePath (did we? or did that never go into the stdlib?), maybe we can preemptively avoid the same debate here.

On Wed, Feb 22, 2012 at 3:05 PM, Evan Laforge
It's awkward and surprising how getEnv throws an exception when an env var is not set, since it's very common for env vars to be optional, and it's not convenient to catch exceptions in haskell (I have to look up Control.Exception every time, and then add LANGUAGE
For true IO exceptions, you should be using the Prelude.catch mechanism. It's the right thing to do and makes your program more portable as a bonus. It is quite unfortunate that IO exceptions became conflated with imprecise and dynamic exceptions and clash in their names. They really are different beasts alltogether. In particular, IO can be fully understood via the following desugaring. data IO a = IO (State -> (State,Either IOException a)) imprecise exceptions break this cognitive desugaring and cause odd things like throwIO and raise having slightly different and confusing syntax and APIs being unclear on what they mean by 'exception'. In any case, they will definitely be completely different beasts in jhc if I decide to implement imprecise exceptions. John

On Wed, Feb 22, 2012 at 18:51, John Meacham
On Wed, Feb 22, 2012 at 3:05 PM, Evan Laforge
wrote: It's awkward and surprising how getEnv throws an exception when an env var is not set, since it's very common for env vars to be optional, and it's not convenient to catch exceptions in haskell (I have to look
For true IO exceptions, you should be using the Prelude.catch mechanism.
This still assumes that it's an exceptional condition that is being modeled. It is not; it is correctly modeled as a Maybe. -- brandon s allbery allbery.b@gmail.com wandering unix systems administrator (available) (412) 475-9364 vm/sms

Brandon Allbery
On Wed, Feb 22, 2012 at 18:51, John Meacham
wrote: On Wed, Feb 22, 2012 at 3:05 PM, Evan Laforge
wrote: It's awkward and surprising how getEnv throws an exception when an env var is not set, since it's very common for env vars to be optional, and it's not convenient to catch exceptions in haskell (I have to look
For true IO exceptions, you should be using the Prelude.catch mechanism.
This still assumes that it's an exceptional condition that is being modeled. It is not; it is correctly modeled as a Maybe.
+1. And it’s always irritated me that read at EOF raises an exception. Files having ends is not exceptional! (This is partly addressed by iteratees, I imagine, but I haven’t found time to get my head round those yet.) -- Jón Fairbairn Jon.Fairbairn@cl.cam.ac.uk

On 2/23/12 4:07 AM, Jon Fairbairn wrote:
+1. And it’s always irritated me that read at EOF raises an exception. Files having ends is not exceptional! (This is partly addressed by iteratees, I imagine, but I haven’t found time to get my head round those yet.)
As John Lato mentioned, unix-bytestring gets around that (and a number of other ugly issues in the API). [1] http://hackage.haskell.org/package/unix-bytestring -- Live well, ~wren

On Wed, Feb 22, 2012 at 3:51 PM, John Meacham
On Wed, Feb 22, 2012 at 3:05 PM, Evan Laforge
wrote: It's awkward and surprising how getEnv throws an exception when an env var is not set, since it's very common for env vars to be optional, and it's not convenient to catch exceptions in haskell (I have to look up Control.Exception every time, and then add LANGUAGE
For true IO exceptions, you should be using the Prelude.catch mechanism. It's the right thing to do and makes your program more portable as a bonus. It is quite unfortunate that IO exceptions became conflated with imprecise and dynamic exceptions and clash in their names. They really are different beasts alltogether.
Good point, I had forgotten about the Prelude catch. I think I can go remove a bunch of ScopedTypedVariables now. The ghc exception situation is a bit messy, and I never have all the distinctions quite memorized, but it's also because I hardly ever have to deal with exceptions (of the "magical return value" variety, I use Either and Maybe all the time), unlike certain other languages. And that's a very good thing! If I forget the rest about exceptions due to lack of use, I should at least remember that IO exceptions are different from, and better behaved than, the imprecise ones.

Am 23.02.2012 01:07, schrieb Evan Laforge:
On Wed, Feb 22, 2012 at 3:51 PM, John Meacham
wrote: On Wed, Feb 22, 2012 at 3:05 PM, Evan Laforge
wrote: It's awkward and surprising how getEnv throws an exception when an env var is not set, since it's very common for env vars to be optional, and it's not convenient to catch exceptions in haskell (I have to look up Control.Exception every time, and then add LANGUAGE
For true IO exceptions, you should be using the Prelude.catch mechanism. It's the right thing to do and makes your program more portable as a bonus. It is quite unfortunate that IO exceptions became conflated with imprecise and dynamic exceptions and clash in their names. They really are different beasts alltogether.
Good point, I had forgotten about the Prelude catch. I think I can go remove a bunch of ScopedTypedVariables now.
This situation is particularly annoying since hlint (1.8.24) always says: ... Error: Use Control.Exception.catch Found: catch Why not: Control.Exception.catch Note: Prelude.catch does not catch most exceptions And now I learn that Prelude.catch is the right version for i.e. file IO (so I should probably switch off that hlint "Error"). Regarding the actual proposal, I fully agree. I always use a wrapper based on getEnvironment. getEnvSave :: a -- ^ default value -> String -- ^ name of environment variable -> (String -> Maybe a) -- ^ parse and check value -> IO a getEnvSave defValue envVar readFun = liftM (maybe defValue (fromMaybe defValue . readFun) . lookup envVar) getEnvironment Cheers Christian
The ghc exception situation is a bit messy, and I never have all the distinctions quite memorized, but it's also because I hardly ever have to deal with exceptions (of the "magical return value" variety, I use Either and Maybe all the time), unlike certain other languages. And that's a very good thing!
If I forget the rest about exceptions due to lack of use, I should at least remember that IO exceptions are different from, and better behaved than, the imprecise ones.

On Thu, Feb 23, 2012 at 09:17:00AM +0100, Christian Maeder wrote:
And now I learn that Prelude.catch is the right version for i.e. file IO (so I should probably switch off that hlint "Error").
I'd disagree that using Prelude.catch is right. I think what John really wants to distinguish between is exceptions thrown in the IO monad, e.g. by Control.Exception.throwIO, from exceptions that may be thrown from pure code, e.g. by Control.Exception.throw. But you can throw exceptions of any type with either function. Thanks Ian

On 23 February 2012 01:07, Evan Laforge
Good point, I had forgotten about the Prelude catch. I think I can go remove a bunch of ScopedTypedVariables now.
Note that Prelude.catch has been deprecated in favor of Control.Exception.catch. If you want to catch IOErrors you can either use ScopedTypedVariables or System.IO.Error.catchIOError. Bas

On Thu, Feb 23, 2012 at 8:18 AM, Bas van Dijk
On 23 February 2012 01:07, Evan Laforge
wrote: Good point, I had forgotten about the Prelude catch. I think I can go remove a bunch of ScopedTypedVariables now.
Note that Prelude.catch has been deprecated in favor of Control.Exception.catch. If you want to catch IOErrors you can either use ScopedTypedVariables or System.IO.Error.catchIOError.
Control.Exception.catch is not in haskell 98 or haskell 2010. It also requires pulling in some heavy extensions and actually has fundamentally different behavior than Prelude.catch. We definitely should not consider it a replacement. John

On 23/02/2012 21:05, John Meacham wrote:
On Thu, Feb 23, 2012 at 8:18 AM, Bas van Dijk
wrote: On 23 February 2012 01:07, Evan Laforge
wrote: Good point, I had forgotten about the Prelude catch. I think I can go remove a bunch of ScopedTypedVariables now.
Note that Prelude.catch has been deprecated in favor of Control.Exception.catch. If you want to catch IOErrors you can either use ScopedTypedVariables or System.IO.Error.catchIOError.
Control.Exception.catch is not in haskell 98 or haskell 2010. It also requires pulling in some heavy extensions and actually has fundamentally different behavior than Prelude.catch. We definitely should not consider it a replacement.
We should consider Control.Exception.catch a generalisation of Prelude.catch. Having both exist was confusing, which is why Prelude.catch was deprecated. It does have a direct replacement: System.IO.Error.catchIOError. catch by itself doesn't require heavy extensions, but it is a bit easier to use with pattern types signatures (which is only available with ScopedTypeVariables, but that's because we're too lazy to separate them.) The right getEnv is easily available as (tryIOError . getEnv). I don't disagree with this proposal though - I think we added getEnv at a time when we were feeling all gung-ho about exceptions, in retrospect this was a bad place to use them. Cheers, Simon

+1
On Wed, Feb 22, 2012 at 6:05 PM, Evan Laforge
It's awkward and surprising how getEnv throws an exception when an env var is not set, since it's very common for env vars to be optional, and it's not convenient to catch exceptions in haskell (I have to look up Control.Exception every time, and then add LANGUAGE ScopedTypeVariables). I wind up using 'fmap lookup k Environment.getEnvironment' instead.
So I propose a function: lookupEnv :: String -> IO (Maybe String)
Then getEnv can be deprecated and eventually removed. Or maybe not if people want to argue the current getEnv is useful and deprecation warnings would be too annoying.
Yeah, it's a trivial thing, but it's nice to sand off some of these rough edges.
Discussion: 2 weeks, March 7
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries

+1, particularly having run into this about 20 minutes ago.
On Wed, Feb 22, 2012 at 6:05 PM, Evan Laforge
It's awkward and surprising how getEnv throws an exception when an env var is not set, since it's very common for env vars to be optional, and it's not convenient to catch exceptions in haskell (I have to look up Control.Exception every time, and then add LANGUAGE ScopedTypeVariables). I wind up using 'fmap lookup k Environment.getEnvironment' instead.
So I propose a function: lookupEnv :: String -> IO (Maybe String)
Then getEnv can be deprecated and eventually removed. Or maybe not if people want to argue the current getEnv is useful and deprecation warnings would be too annoying.
Yeah, it's a trivial thing, but it's nice to sand off some of these rough edges.
Discussion: 2 weeks, March 7
_______________________________________________ Libraries mailing list Libraries@haskell.org http://www.haskell.org/mailman/listinfo/libraries

So I propose a function: lookupEnv :: String -> IO (Maybe String)
We already have System.Posix.Env.getEnv :: String -> IO (Maybe String) So for consistency I'd advocate for renaming that to lookupEnv, too. (keeping getEnv as a deprecated alias, for backward compatibility). Other than that, +1. Cheers, Simon

Evan Laforge wrote:
It's awkward and surprising how getEnv throws an exception when an env var is not set, since it's very common for env vars to be optional, and it's not convenient to catch exceptions in haskell (I have to look up Control.Exception every time, and then add LANGUAGE ScopedTypeVariables). I wind up using 'fmap lookup k Environment.getEnvironment' instead.
So I propose a function: lookupEnv :: String -> IO (Maybe String)
+1 Erik -- ---------------------------------------------------------------------- Erik de Castro Lopo http://www.mega-nerd.com/

On Wed, Feb 22, 2012 at 3:05 PM, Evan Laforge
It's awkward and surprising how getEnv throws an exception when an env var is not set, since it's very common for env vars to be optional, and it's not convenient to catch exceptions in haskell (I have to look up Control.Exception every time, and then add LANGUAGE ScopedTypeVariables). I wind up using 'fmap lookup k Environment.getEnvironment' instead.
So I propose a function: lookupEnv :: String -> IO (Maybe String)
Then getEnv can be deprecated and eventually removed. Or maybe not if people want to argue the current getEnv is useful and deprecation warnings would be too annoying.
Yeah, it's a trivial thing, but it's nice to sand off some of these rough edges.
Discussion: 2 weeks, March 7
Well, it's past March 7, and there was pretty widespread support, so I made a patch and and ticket: http://hackage.haskell.org/trac/ghc/ticket/5930 Anything else I should do?
participants (16)
-
Adam Foltzer
-
Bas van Dijk
-
Brandon Allbery
-
Christian Maeder
-
Erik de Castro Lopo
-
Evan Laforge
-
Gwern Branwen
-
Herbert Valerio Riedel
-
Ian Lynagh
-
Johan Tibell
-
John Meacham
-
Jon Fairbairn
-
Ryan Newton
-
Simon Hengel
-
Simon Marlow
-
wren ng thornton