Haskell Best Practise: Early termination in Haskeline
Asked Answered
L

2

5

I am using the Haskeline package and I want to get three strings in a row from the command line before I do anything and I have come up with what seems to be a neat solution to me. But I am sure that there might be a better way to do it. I am looking for best practices while using the Haskeline package. Please evaluate the merits of the following example code:

import System.Console.Haskeline
import Control.Monad.Trans
import Control.Monad.Maybe
import Data.Maybe
import Control.Monad

main :: IO ()
main = runInputT defaultSettings (runMaybeT getStrings) >>= print

getStrings :: MaybeT (InputT IO) (String, String, String)
getStrings = do
   mone <- lift $ getInputLine "food> "
   notNothing mone
   mtwo <- lift $ getInputLine "drink> "
   notNothing mtwo
   mthree <- lift $ getInputLine "dessert> "
   notNothing mthree
   return (fromJust mone, fromJust mtwo, fromJust mthree)
      where
         notNothing a = guard (a /= Nothing)

As you can see it accomplishes the task of early termination but it looks a bit yucky still. I'm thinking of trying to convert the notNothing's and the getInputLine's into a single line like:

mone <- notNothing =<< lift $ getInputLine "food> " -- does not type check

Which I think does not look that bad. I think that is pretty clear and concise (though it does not type check so I will have to write a version that does).

However, this is the best I have come up with and my question finally is: How would you go about improving this code to be neater and more readily readable? Am I even on the right track?

Edit: If your guard is something other than 'a /= Nothing' then a nice helper function that I just discovered is:

myGuard s = guard (someConditionFunc s) >> s

Because then you can write (as luqui suggested):

mone <- myGuard =<< (lift $ getInputLine prompt)

Which is pretty cool. But if you are matching against only Nothing then TomMD's answer is better.

Logsdon answered 22/1, 2011 at 23:2 Comment(3)
I think your combined line doesn't work because of the precedence of $; try mone <- notNothing =<< lift (getInputLine "food> ")Glottochronology
If you're interested in best practices in general, you should use hlint on your code.Kurland
@amccausl I do but it would not have suggested this I don't think.Logsdon
P
7

Why not just leverage the fact that fail _ = Nothing for the Maybe monad?

mthree <- lift $ getInputLine "dessert> "
notNothing mthree

becomes

Just mthree <- lift $ getInputLine "dessert> "
Pirnot answered 22/1, 2011 at 23:14 Comment(1)
Very Nice!! It seems blindingly obvious in retrospect! Any more excellent suggestions? (Though I doubt it can get much smaller than it is now)Logsdon
P
4

How about a helper function?

inputLine :: String -> MaybeT (InputT IO) String
inputLine prompt = do
    m <- lift $ getInputLine prompt
    case m of
        Just x -> return x
        Nothing -> mzero

This can be shortened considerably using various tricks, but I wanted to be clear. Now you can just forget that getInputLine can fail, MaybeT takes care of that for you.

Projectile answered 22/1, 2011 at 23:16 Comment(1)
Your right! The best helper function (imo) is 'myGuard a = guard (condition_with_a) >> return a' because then you can say 'm <- myGuard =<< (lift $ getInputLine prompt)'Logsdon

© 2022 - 2024 — McMap. All rights reserved.