4 ms·
The Haskell examples at the beginning are a bit weirdly chosen. You wouldn't want to write code like that except in scripts etc. because it would crash the prog
by Tainnor 2y ago
The Haskell examples at the beginning are a bit weirdly chosen. You wouldn't want to write code like that except in scripts etc. because it would crash the program if you encounter bad input.
The first example could be more idiomatically written as:
strSum :: String -> Maybe Int
strSum = fmap sum . sequence . fmap readMaybe . words
(You'd also probably want to avoid String and use Text instead.)
For more complex parsing scenarios, the various parser combinator libraries can take a while to get used to (and I wish the community would standardise on one or two of them), but they're extremely powerful.
- runeks 2y agoLet's take that further: you wouldn't want to use your code above either, because it would be impossible to tell why the parser failed. Which is going to be really frustrating once it fails. And if it doesn't fail the first version is fine.
- Tainnor 2y agoSure, you can improve this by e.g. import Data.Either.Combinators -- from the either package strSum :: String -> Either String Int strSum = fmap sum . sequence . fmap tryReadInt . words where tryReadInt w = maybeToRight ("not an integer " ++ w) (readMaybe w) This keeps the bulk of the method the same. Being able to go e.g. from Maybe to Either with only few changes (as long as your code is sufficiently general) is one of the nice things you get from all the Haskell abstraction. You can't really do that if you start with exceptions (unless you're in IO).
- brabel 2y agoHm... for anything real, you would need to provide good error reports, i.e. why did it fail to parse. Code like this looks pretty, but once you add in good error reports, I feel like it tends to look almost exactly the same as in an imperative language?
- mrkeen 2y agostrSum :: String -> Either String Int strSum = fmap sum . mapM readEither . words
- Tainnor 2y agoThat doesn't exactly give a good error message, though: > strSum "10 20 x" Left "Prelude.read: no parse" But yeah, I didn't remember "mapM". mapM f = sequence . fmap f, which is what I used.
- Tainnor 2y agoFor a real use case, consider using a parser library. If you ignore the boilerplate import stuff, it's IMHO rather short and elegant - and not very imperative: {-# LANGUAGE OverloadedStrings #-} import Data.Text (Text) import Data.Void (Void) import qualified Data.Text as T import Text.Megaparsec import Text.Megaparsec.Char import qualified Text.Megaparsec.Char.Lexer as L type Parser = Parsec Void Text numParser :: Parser [Integer] numParser = L.decimal `sepBy` space1 -- just for demonstration purposes - this will print an ugly debug string, it can be customised main = putStrLn $ show $ parse numParser "some-source-file.txt" "10 20 30 x" -- this would print "Right [10, 20, 30]" -- main = putStrLn $ show $ parse numParser "some-source-file.txt" "10 20 30"