I'm very new to Haskell, I've read most of learnyouahaskell.com and played around with some simple things, but this is probably the most 'complicated' bit of Haskell I've written so far. I have an implementation in PHP that does basically the same thing, but it is much much faster.. I'm guessing my bottleneck here is that randomRIO takes a long time to return a value, is there any way to increase the speed, or get an array of values instead of grabbing them one at a time?
That aside, any other tips and/or suggestions about improving my implementation would be very helpful!
module Main where
import System.Environment
import System.Exit
import System.Random (randomRIO)
import Control.Monad (replicateM)
import qualified Data.Map as M
import Text.Regex.Posix
main :: IO ()
main = do
args <- getArgs
let helpOnly = "-h" `elem` args
if helpOnly
then putStrLn usage
else do
password <- generatePassword ("-w" `elem` args)
putStrLn password
exitSuccess
usage :: String
usage = unlines [
"",
"Usage: [ -w, --with-symbols ] [ -h, --help ]",
"",
"By default, generates a 16 character password that does not include symbols.",
"",
"-w, --with-symbols include symbols",
"-h, --help print a brief help message"
]
symbols :: String
symbols = "!$%^&*()-_=+[{]};:@#~,<.>/?"
randomReplace :: String -> String -> IO String
randomReplace [] subject = return subject
randomReplace (replacement:rs) subject = do
randomIndex <- randomRIO (0, length subject - 1) :: IO Int
let hash = zip [0 .. length subject - 1] subject
(randomReplace rs . map snd . M.toList . M.insert randomIndex replacement . M.fromList) hash
generatePassword :: Bool -> IO String
generatePassword withSymbols = do
let passwordLength = 500 -- obviously you wouldn't use 500 as a default here, but I'm just benchmarking
numDigits <- randomRIO (1, passwordLength) :: IO Int
digits <- replicateM numDigits (randomRIO (1, 9) :: IO Int)
numUppercase <- randomRIO (1, passwordLength) :: IO Int
uppercaseLetters <- replicateM numUppercase (randomRIO ('A', 'Z') :: IO Char)
p1 <- replicateM passwordLength (randomRIO ('a', 'z') :: IO Char)
p2 <- randomReplace (concatMap show digits) p1
p3 <- randomReplace uppercaseLetters p2
password <- if withSymbols
then do
numSymbols <- randomRIO (1, passwordLength) :: IO Int
symbolsToReplace <- replicateM numSymbols ((randomRIO (0, length symbols - 1) :: IO Int) >>= (\x -> return $ symbols !! x))
randomReplace symbolsToReplace p3
else return p3
if (password =~ "[a-z]" :: Bool) && (password =~ "[A-Z]" :: Bool) && (password =~ "[0-9]" :: Bool) -- we knows symbols are in there since it went last
then return password
else generatePassword withSymbols