# Recognizing lists

Published on

```
import Data.Monoid
import Data.List (isSuffixOf)
import Control.Applicative
import Control.Applicative.Permutation as Perm
import Control.Monad.Trans.State
```

You are given the following Haskell data type:

`data T = A Int | B String`

Your task is to write a function

`recognize :: [T] -> Bool`

that would accept a list iff it contains exactly three elements, of which:

- one is an
`A`

with a value between 3 and 7 - one is an
`A`

with an even value - one is a
`B`

with a value ending in`.hs`

The `A`

elements mentioned in conditions (1) and (2) have to be distinct, so `[A 4, A 17, B "Prelude.hs"]`

should not be accepted, even though `A 4`

satisfies both (1) and (2).

This problem often arises in blackbox testing. You do something to a system; you wait for some time while gathering all events that the system generates. Then you want to check that the system generated all the events it had to generate, and no extra ones. However, the order of events may be non-deterministic. If the system concurrently makes a web request, updates a database and sends a message to a message bus, the order in which these events will be observed by your test suite is arbitrary.

A similar problem arises in input validation.

## Interface

We want to solve this problem in a general and scalable way. What would an interface to such solution look like? There are two principal options.

One is a simple function

`recognize :: [t -> Bool] -> [t] -> Bool`

that takes individual predicates and combines them into a recognizer.

Another is to split the process into the *build* and *use* phases by the means of an intermediate data type:

```
data Parser t
instance Monoid Parser
toParser :: (t -> Bool) -> Parser t
runParser :: Parser t -> [t] -> Bool
```

In this approach, you convert individual recognizers to monoidal values, combine those values and extract the result.

Semantically, the two are equivalent: if you have one of these interfaces available, you can implement the other one. In a way, this is a consequence of lists being the free monoid, but writing this down explicitly is a good exercise nevertheless.

The advantages of a *build-use* interface are:

- When the cost of the
*build*phase is non-trivial, it can be amortized over multiple uses and/or incremental builds. (This won’t be the case here.) - The use of a specialized type is generally good for the type system hygiene.

We’ll start with the *build-use* interface because I find it more instructive.

## The Parser

To build the monoidal parser, we’ll use the action-permutations library. You may remember it from the article on JSON parsing.

`newtype Parser t = Parser { getParser :: Perms (StateT [t] Maybe) () }`

`StateT [t] Maybe a`

is the familiar type of parser combinators, `[t] -> Maybe (a, [t])`

, that return the left-over input. We wrap it in `Perms`

to achieve the order independence, and then newtype it to convert its Applicative instance to a Monoid one:

```
instance Monoid (Parser t) where
mempty = Parser (pure ())
mappend (Parser a) (Parser b) = Parser $ a *> b
```

The build and use functions are straightforward:

```
toParser :: (t -> Bool) -> Parser t
= Parser $ Perm.atom $ StateT $ \ts ->
toParser isX case ts of
:ts') | isX t -> Just ((), ts')
(t-> Nothing
_
runParser :: Parser t -> [t] -> Bool
=
runParser p ts case (flip execStateT ts . runPerms . getParser) p of
Just [] -> True
-> False _
```

## Usage

First, let’s express our conditions in Haskell code.

```
isB :: T -> Bool
isA1, isA2,
isA1 x| A n <- x, 3 <= n, n <= 7 = True
| otherwise = False
isA2 x| A n <- x, even n = True
| otherwise = False
isB x| B s <- x, ".hs" `isSuffixOf` s = True
| otherwise = False
```

Combine them together:

```
parser :: Parser T
= (mconcat . map toParser) [isA1, isA2, isB] parser
```

(we could replace `mconcat . map toParser`

with `foldMap toParser`

from `Data.Foldable`

)

```
recognize :: [T] -> Bool
= runParser parser recognize
```

Now try it out:

```
= recognize [A 4, A 17, B "Prelude.hs"]
ex1 -- => False
= recognize [A 4, A 18, B "Prelude.hs"]
ex2 -- => True
= recognize [B "Prelude.hs", A 18, A 4]
ex3 -- => True
= recognize [B "Prelude.hs", A 18, A 4, A 1]
ex4 -- => False
```