Search code examples
haskellmonadsparsec

Refactoring where clause


I am learnig Haskell, so it's probably something pretty trivial, but I would appreciate some pointers on how to rewrite it and how it works.

I have following working code (used packages: HTF, Parsec and Flow):

{-# OPTIONS_GHC -F -pgmF htfpp #-}
{-# LANGUAGE FlexibleContexts #-}

module Main where

import Test.Framework -- assertEqual, assertBool, htfMain, htf_thisModulesTests
import Text.ParserCombinators.Parsec (eof, spaces, parse)
import Flow ((|>))
import Data.Either (isLeft)

whiteSpaces = spaces

test_parse_whitespace = do
  mapM_ positive [
      "", " ", "\t", "\n", "\r\n", "  \r\n  ",
      "   \t   \r\n  \t  \n   \r  \t "
    ]
  mapM_ negative ["x",  " x",  "x ",  " x ",  "\t_\t"]
  where
    parser = whiteSpaces >> eof
    parseIt = parse parser ""
    positive str = assertEqual (parseIt str) (Right ())
    negative str = assertBool (parseIt str |> isLeft)

main :: IO ()
main = htfMain htf_thisModulesTests

I am adding a new test which have almost same the where part, so I tried to refactor it like this:

pos_neg_case parser = do
  return [positive, negative]
  where
    fullParser = parser >> eof
    parseIt = parse fullParser ""
    positive str = assertEqual (parseIt str) (Right ())
    negative str = assertBool (parseIt str |> isLeft)

test_parse_whitespace' = do
  mapM_ positive [
      "", " ", "\t", "\n", "\r\n", "  \r\n  ",
      "   \t   \r\n  \t  \n   \r  \t "
    ]
  mapM_ negative ["x",  " x",  "x ",  " x ",  "\t_\t"]
  where
    [positive, negative] = pos_neg_case whiteSpaces

Which doesn't work (even when I turn the lang. feature on as compiler suggests).

Couldn't match expected type ‘[Char] -> m b0’
            with actual type ‘[String -> IO ()]’
Relevant bindings include
  test_parse_whitespace' :: m () (bound at test/Spec.hs:21:1)
In the first argument of ‘mapM_’, namely ‘positive’
In a stmt of a 'do' block:
  mapM_ positive ["", " ", "\t", "\n", ....]

Couldn't match expected type ‘[Char] -> m b1’
            with actual type ‘[String -> IO ()]’
Relevant bindings include
  test_parse_whitespace' :: m () (bound at test/Spec.hs:21:1)
In the first argument of ‘mapM_’, namely ‘negative’
In a stmt of a 'do' block:
  mapM_ negative ["x", " x", "x ", " x ", ....]

Solution

  • As you have noticed, the problem was the return you added to:

    pos_neg_case parser = do
      return [positive, negative]
      where -- etc.
    

    The type of mapM_ is:

    GHCi> :t mapM_
    mapM_ :: (Foldable t, Monad m) => (a -> m b) -> t a -> m ()
    

    positive and negative are functions which already have appropriate types to be passed to mapM, and so if you want pos_neg_case to give them back as a list you don't need to do anything more than wrapping in a list. return is not a keyword; it is just a function that injects a value into a monadic context. If you don't need to do any such injection, you don't need return.

    P.S.: Quoting your answer:

    But I had to guess the Parser type, hole was giving me very complex thingy Text.Parsec.Prim.ParsecT s () Data.Functor.Identity.Identity a -> [s -> IO ()].

    This is an example of a quite common pattern. ParsecT is a type constructor with quite a few type variables, and Parser is a type synonym for a common set of choices of some of these variables, which allows for tidier type signatures which do not mention them explicitly. If you look for it in the documentation (the index helps a lot in such cases) or use :info in GHCi, you will find that Parser just means...

    type Parser = Parsec String ()
    

    ... and Parsec, in turn is...

    type Parsec s u = ParsecT s u Identity
    

    ... so that expanding the Parser synonym gives ParsecT String () Identity, which is what GHC told you when you introduced the type hole.