Is there any way to shorten these lines

Viewed 95
type Parser a = String -> Maybe (a, String)

parseChar :: Char -> Parser Char
parseChar _ "" = Nothing
parseChar ch (x:xs) | x == ch = Just (ch, xs)
                    | otherwise = Nothing

parseAnyChar :: String -> Parser Char
parseAnyChar "" _ = Nothing
parseAnyChar _ "" = Nothing
parseAnyChar (x:xs) str | isJust res = res
                        | otherwise = parseAnyChar xs str
                        where res = parseChar x str

I'm a beginner in haskell and I would like to know how can I use parseChar in parseAnyChar in a more "haskell way" than looping recursively. Using a map for example or anything else but I can't find.

2 Answers

Yes. For one thing, you can just use a standard function instead of manual recursion, to try parseChar on all the specified alternatives:

import Data.List (find)

parseAnyChar cs str = case find isJust ress of
    Just res -> res
    Nothing  -> Nothing
 where ress = [parseChar c str | c<-cs]

...or shorter

import Data.Maybe (listToMaybe)
import Control.Monad (join)

parseAnyChar cs str = join . listToMaybe $ (`parseChar`str)<$>cs

A more efficient solution is to not attempt all the options in the first place, but instead use a Set of options:

import qualified Data.Set as Set

parseOneCharOf :: Set.Set Char -> Parser Char
parseOneCharOf _ "" = Nothing
parseOneCharOf cs (x:xs)
   | x`Set.member`cs  = Just x
   | otherwise        = Nothing

...or shorter

import Control.Monad (guard)
parseOneCharOf cs (x:xs) = guard (x`Set.member`cs) >> Just x

You can make use of Alternative instance of Maybe:

import Control.Applicative((<|>))

parseAnyChar :: Foldable f => f Char -> Parser Char
parseAnyChar xs str = foldr ((<|>) . (`parseChar` str)) Nothing xs

This works since, (<|>) for Maybe is implemented as [src]:

instance Alternative Maybe where
    empty = Nothing
    Nothing <|> r = r
    l       <|> _ = l

So in case the left operand is Nothing, we pick the right one, if the left one is a Just …, we pick the first one.

We thus can rewrite the parseAnyChar to:

parseAnyChar :: String -> Parser Char
parseAnyChar "" _ = Nothing
parseAnyChar _ "" = Nothing
parseAnyChar (x:xs) str = parseChar x str <|> parseAnyChar xs str

We do not need a special case for the second parameter being "" however, since this is already covered by the logic in parseChar. We thus can drop the second clause:

parseAnyChar :: String -> Parser Char
parseAnyChar [] _ = Nothing
parseAnyChar (x:xs) str = parseChar x str <|> parseAnyChar xs str

and now we can rewrite this to a foldr pattern.

This also makes it possible to use an Foldable as source of characters.

If the number of items is however large, you might want to use a more efficient data structure, like @leftroundabout suggests.

Related