3

DISCLAIMER: I am somewhat new to Haskell.

I am writing an interpreter, or, in this context, a REPL. For that purpose I am using haskeline, which is nice for REPLs. It has the capability of storing the command line history within a file, which is also nice.

One problem I came across while working with it, though, is that it does not seem to expand "~" to the home directory, which means that I have to retrieve the home directory manually.

I could do it like this (and currently do):

-- | returns a fresh settings variable
addSettings :: Env -> Settings IO
addSettings env = Settings { historyFile = Just getDir
                           , complete = completeWord Nothing " \t" $
                                        return . completionSearch env
                           , autoAddHistory = True
                           }
    where
        getDir :: FilePath
        getDir = unsafePerformIO getHomeDirectory ++ "/.zepto_history"

But that uses unsafePerformIO, which makes me cringe. Do you know of a good and clean workaround that does not involve rewriting the whole function? This can be a haskeline feature I do not know of or something I just did not see.

Telling me there is no way around rewriting and rethinking it all is fine, too.

EDIT:

I know unsafePerformIO is bad, that's why it makes me cringe. If you are new to Haskell and reading this question right now: Just pretend it is not there.

peterh
  • 11,875
  • 18
  • 85
  • 108
hellerve
  • 508
  • 1
  • 6
  • 30
  • Could you not just generate the settings inside `IO`, as in `addSettings env = do { homeDir <- getHomeDirectory; return $ Settings { historyFile = Just (homeDir ++ "/.zepto_historY"), ... }`? Since you're new to Haskell I'll give the typical warning of **DON'T USE `unsafePerformIO`!** It's unsafe (obviously) and can lead to very unexpected behavior. It's not part of the Haskell language even, it's part of the GHC implementation and is useful mainly when interfacing with C libraries or when working with low level features. – bheklilr Apr 10 '15 at 18:56
  • I know that. That's why it makes me cringe. – hellerve Apr 10 '15 at 18:58
  • **Never ever perform unsafe IO!** Learn about the IO monad: http://learnyouahaskell.com/input-and-output – AJF Apr 10 '15 at 18:58
  • Just trying to drive the point home. There is almost always an alternative, such as the one i suggested above. Would that approach work for you? – bheklilr Apr 10 '15 at 18:59
  • 1
    I just tried it out. I had to rewrite a few minor things, but it worked. Thanks, such a simple solution; I just had this: "You could have found that out yourself" moment which always comes with good answers. Would you add the answer, so I can accept it? – hellerve Apr 10 '15 at 19:04
  • @AJFarmar: I agree, but for the record, this doesn't mean it's a good idea to write everything in the `IO` monad. On the contrary, first consideration should be avoiding IO that isn't really necessary at all. – leftaroundabout Apr 10 '15 at 23:00
  • 1
    In general unsafe IO is a bad idea, but `unsafePerformIO getHomeDirectory` is pretty safe (unless your operating system likes to change your home directory while your process is still running) – Jeremy List Apr 21 '15 at 01:41
  • I thought as much, but I wanted to avoid it if possible, because it is bad practice. – hellerve Apr 21 '15 at 07:34

1 Answers1

5

A better approach would be to generate the Settings object inside IO, instead of the other way around, so to speak:

addSettings :: Env -> IO (Settings IO)
addSettings = do
    getDir <- fmap (++ "/.zepto_history") getHomeDirectory
    return $ Settings
        { historyFile = Just getDir
        , complete = completeWord Nothing " \t" $ return . completionSearch env
        , autoAddHistory = True
        }

This will no doubt require some changes in your current software, but this would be considered the "right" way to go about this.

bheklilr
  • 53,530
  • 6
  • 107
  • 163