Last active
July 7, 2020 13:48
-
-
Save ObjectBoxPC/7dbdfedc2482d364a41445e5322e54ff to your computer and use it in GitHub Desktop.
Simple Haskell program to check if the input (command-line arguments) form a pangram, containing all 26 letters of the English alphabet
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Data.Char (toUpper) | |
import Data.List (intercalate) | |
import Data.List.NonEmpty (NonEmpty, nonEmpty, toList) | |
import System.Environment (getArgs) | |
data PangramResult = Pangram | MissingLetters (NonEmpty Char) | |
checkPangram :: String -> PangramResult | |
checkPangram s = case nonEmpty missingLetters of | |
Just ls -> MissingLetters ls | |
Nothing -> Pangram | |
where | |
missingLetters = filter (not . (`elem` normalized)) ['A'..'Z'] | |
normalized = map toUpper s | |
getText :: IO String | |
getText = concat <$> getArgs | |
showLetters :: NonEmpty Char -> String | |
showLetters = intercalate ", " . map (:[]) . toList | |
main = do | |
text <- getText | |
let | |
message = case checkPangram text of | |
Pangram -> "Pangram" | |
MissingLetters ls -> "Not a pangram: Missing " ++ showLetters ls | |
putStrLn message |
@friedbrice Thanks a lot for your detailed comments. I've made changes based on your suggestions.
Some specific responses:
- I originally had something very similar to your alternative implementation of
allElems
/subset
, but I suppose I went a bit overboard in trying to make it point-free. I wasn't used to using functions as infix operators so I had(flip elem)
instead of(`elem` xs)
However, thinking ofelem
andsubset
as set symbols does make the infix version more intuitive to me. - My implementation of
main
was meant to convey a sort of pipeline like in a Unix shell. But do-notation version does make it shorter so I went for that.
@ObjectBoxPC I'm glad it was helpful. I definitely relate to the tendency to want to write your main
as a pipeline, so I tend to favor the putStrLn . msg . concat =<< getArgs
style. It never bothered me that the entrance to the pipeline is on the right side, because that's how function application is written in every programming language and traditionally on paper.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Idiomatic Haskell requires type signatures on top-level functions. (Type inference is awesome, and it's for lambdas, intermediate expressions, and temporary names, not for top-level functions.) You'd see:
That would help the reader readily apprehend the meaning. Furthermore, I could try to use the name to convey the positional relation between the arguments, e.g. intead of
allElems ys xs
, change the name and write it infix asBetween the name and the type signature, the meaning of the function and the order of its arguments should be clear. Users shouldn't have to look at the implementation. That way, you can have your cake and eat it too.
Now it doesn't matter that the implementation is quite erudite, because I no longer need to understand the implementation. I can still use this function because I know both its name and its type signature. I would know to write my code as
(By the way, the
['A' .. 'Z']
is legal Haskell that produces the string you need.)