haskellsome-and-manyalternative-functor

Haskell - some, many implementation


In the article: "Write you a Haskell" (page 34) the following interpretation of "some" and "many" is given:

Derived automatically from the Alternative typeclass definition are the many and some functions. many takes a single function argument and repeatedly applies it until the function fails, and then yields the collected results up to that point. The some function behaves similar except that it will fail itself if there is not at least a single match.

-- | One or more.
some :: f a -> f [a]
some v = some_v
  where
    many_v = some_v <|> pure []
    some_v = (:) <$> v <*> many_v

-- | Zero or more.
many :: f a -> f [a]
many v = many_v
  where
    many_v = some_v <|> pure []
    some_v = (:) <$> v <*> many_v

I have been trying to understand this implementation for a while.

I dont understand how "many" and "some" could be applied to "lists" or "Maybe".

Also I am not sure about (:) <$> v <*> many_v.

How does one derive this?


Solution

  • From ghc/libraries/base/GHC/Base.hs there is this recursive declaration:

        ... :: f a -> f [a]
        ...
            many_v = some_v <|> pure []
            some_v = liftA2 (:) v many_v  -- (:) <$> v <*> many_v
    

    The argument v must be a value of a type that is instance of Alternative (and Applicative and Functor). v is lifted already and just needs to be applied (<*>). The application results in a lifted list.

    some and many make recursive applications of the constructor of v, and put the constructed values into a list.

    [] is instance of Alternative:

    instance Alternative [] where
        empty = []
        (<|>) = (++)
    

    some tries with list:

      av = [[2], [2,3], [], [2,3,5]]
      some av                      -- no error, but never stops
      do {v <- some av; return v;} -- no error, but never stops
    

    Comparing to letter in What are Alternative's "some" and "many" useful for?:

      import Control.Monad(Functor(..))
      import Control.Applicative
      import Data.Char
      newtype P a = P { runP :: String -> [(a,String)] }
      instance Functor P where
        fmap f (P q) = P (\s -> [ (f y,ys) | (y,ys) <- q s])
      instance Applicative P where
        pure x = P (\s -> [(x,s)])
        P p <*> P q = P (\s -> [(x y, ys) | (x,xs) <- p s, (y,ys) <- q xs])
      letter = P p where
        p (x:xs) | isAlpha x = [(x,xs)]
        p _ = []
      instance Alternative P where
        P p <|> P q = P (\s-> p s ++ q s)
        empty = P (\s-> [])
    

    with usage:

      runP (many letter) "ab123"
    

    letter is a smart constructor, that constructs a value of P with field runP using local variable p. (The result of the accessor) runP is a function. P is a type implementing Alternative as well as a constructor. P stands for parser.

    fmap is not used in some and many. <*> leads to an application of the function runP, whose argument is not yet here. Basically some and many construct a program that will eat from its argument. The argument must be a list. Due to laziness the recursion stops at the first constructor.

      p = some letter -- constructs a program accessed via @runP@
      runP p "a1" -- [("a","1")]
    
      q = some [2] -- basically also a program
      q -- never stops
    

    Recursive application is not empty ([] for list), because there is no source of criteria to stop, i.e. no input.

    These stop:

      some [] -- []
      many [] -- [[]]
      some Nothing -- Nothing
      many Nothing -- Just []
    

    There are more requirements on the argument of some and many (v).

    Conclusion: some and many cannot be applied to list values, even though list implements Alternative.

    Why does list implement Alternative? I think, it is to add the Monoid interface <|> and empty on top of Applicative, not because of some and many.

      foldr (<|>) [] [[2],[],[3,4]] -- [2,3,4]
    

    some and many seem to be just for parser construction or more generally construction of a program consuming input and producing more outputs, of which some can be empty. That is quite general already. But the place in Alternative is only justified, if most of Alternative instances have a sensible usage for it. That is not the case.