I'm trying to pick up some Haskell skills so thought I'd write a random password generator to get to grips with using IO
. It was far trickier than I expected and I employed rather more trial and error than I'd have liked.
What I've ended up with works, and while I'm happy with it generally I'd like to know if this is reasonable, idiomatic Haskell or if I've done anything peculiar.
import System.Random
-- Generate a random number in closed interval [lo, hi]
random_int :: Int -> Int -> IO Int
random_int lo hi = (randomRIO (lo,hi) :: IO Int)
-- Generate a random character from an internal alphabet
random_char :: IO Char
random_char = do
index <- random_int 0 $ (length alphabet) - 1
return $ alphabet !! index
where alphabet = ['A'..'Z'] ++
['a'..'z'] ++
['0'..'9'] ++
"!\"£$%^&*()-_+={}[]:;@'~#|\\<,>.?/'"
-- Generate a random password of given length
random_password :: Int -> IO String
random_password length =
sequence $ map (\x -> random_char) [1..length]
main :: IO ()
main = random_password 8 >>= putStrLn
The implementation of random_password
feels odd and hacky, but I struggled to find another way to create an array of results from successive invocations of random_char
or any way to avoid the need for sequence
. Is it hacky?
2 Answers 2
hlint
You should run hlint
on your program - it's a good way to become
familiar with techniques and practices used by expert Haskellers.
Running hlint
on your code returned the following suggestions:
* use camelCase instead of underscores
./passwd.hs:7:20: Warning: Redundant bracket
Found:
(randomRIO (lo, hi) :: IO Int)
Why not:
randomRIO (lo, hi) :: IO Int
./passwd.hs:19:5: Error: Use mapM
Found:
sequence $ map (\ x -> random_char) [1 .. length]
Why not:
mapM (\ x -> random_char) [1 .. length]
./passwd.hs:19:21: Warning: Use const
Found:
\ x -> random_char
Why not:
const random_char
sequence . map
You're right that the code for random_passwd
feels hacky since
the value x
is ignored. The canonical way to write this is:
import Control.Monad (replicateM)
random_passwd = replicateM length random_char
replicateM
repeats a monadic action a specified number of times collecting the
results in a list.
!!
You probably already know that using !!
to index into a list
is inefficient. A better data structure to use for alphabet
is
Text
from Data.Text
which gives you O(1) indexing.
import qualified Data.Text as T
alphabet = T.pack $ ['A'..'Z'] ++ ['a'..'z'] ++ ...
random_char = do
index <- randomR (0, T.length alphabet - 1)
return $ T.index alpha index
-
\$\begingroup\$ Thanks, wasn't aware of
hlint
, and if I'd thought to look for a monadic equivalent ofreplicate
I'd have saved myself a lot of time!Data.Text
is a new one too, so all in all lots of useful stuff here :-) \$\endgroup\$boycy– boycy2015年10月03日 23:47:56 +00:00Commented Oct 3, 2015 at 23:47
As a general point of Haskell style, you have "take N from a list" upside down. You are generating a list of numbers 1 to N but ignoring each number (replacing it with a function that takes no arguments) simply because you want N items in the list. It could be N of any given integer. It could be N of anything if you are just going to throw it away and replace it (hint, hint).
The idiomatic (and more flexible/reusable) way for those tasks in general is to generate an infinite list and take N from it (since laziness means that the elements you don't take aren't actually generated).
So both
take length [1..]
and
take length $ repeat 1
would give you your infinite list of integers. The Data.List
module also offers
replicate length 1
Which would give you N copies of the number 1. That's one step closer to what you need.
But you don't want integers, you want your function. By now, you have probably worked out that
replicate length random_char
gives you an N-sized list where each element is your random_char
function. So
sequence $ replicate length random_char
removes the unnecessary numbers and mapping from your solution.
NOTE: Code is data and data is code. You can tread functions like data in a functional languages. Building a list of functions is a perfectly reasonable thing to do.
All that said, Haskell's Control.Monad
library provides the function you really want:
replicateM :: Monad m => Int -> m a -> m [a]
replicateM n act performs the action n times, gathering the results.
-
\$\begingroup\$ Thanks, I'll try to think about that in future when using lists. I was aware at the time that it didn't feel like the right way to do it... \$\endgroup\$boycy– boycy2015年10月03日 23:50:38 +00:00Commented Oct 3, 2015 at 23:50
Explore related questions
See similar questions with these tags.