-
-
Save ObjectBoxPC/7dbdfedc2482d364a41445e5322e54ff to your computer and use it in GitHub Desktop.
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 |
I could not understand the meaning of allElems
as you had it, so I had to write out out. This is what I ended up with.
allElems = flip $ all . flip elem
allElems = (\t ys xs -> t xs ys) (all . (\f u y -> f y u) elem) -- def of `flip`
allElems = (\t ys xs -> t xs ys) (all . (\u y -> elem y u) -- function application
allElems = (\t ys xs -> t xs ys) (\u -> all (\y -> elem y u)) -- function composition
allElems = \ys xs -> (\u -> all (\y -> elem y u)) xs ys -- function application
allElems = \ys xs -> (all (\y -> elem y xs)) ys -- function application
allElems ys xs = all (\y -> elem y xs) ys -- sugar
allElems ys xs = all (\y -> y `elem` xs) ys -- sugar
allElems ys xs = all (`elem` xs) ys -- sugar
In real life, I would expect to see it written as the last line. I doubt very many people would be able to read and readily understand its meaning the way it is on the first line.
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:
allElems :: Eq a => [a] -> [a] -> Bool
allElems = ...
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 as
subset :: Eq a => [a] -> [a] -> Bool
ys `subset` xs = ...
Between 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.
subset :: Eq a => [a] -> [a] -> Bool
subset = flip $ all . flip elem
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
pangram = (['A' .. 'Z'] `subset`) . map toUpper
(By the way, the ['A' .. 'Z']
is legal Haskell that produces the string you need.)
@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.
I would not be surprised if I saw any of these.
But to answer your question more literally,
x >>= return . f
is always the same asfmap f x
.