We haven’t had to parse any operators in our other languages, and I really don’t want to go through the ins and outs of coming up with a grammar and factoring it appropriately (because there are other sources that do it better), so it’s time to introduce some new tricks.

`parsers`

As usual, `parsers`

has something useful for this. In this case it’s contained in `Text.Parser.Expression`

, and we’re going to do a bit of a lightning tour of what that offers.

This provides the data type `Assoc`

:

```
data Assoc =
AssocNone
| AssocLeft
| AssocRight
```

the data type `Operator`

:

```
data Operator m a =
Infix (m (a -> a -> a)) Assoc
| Prefix (m (a -> a))
| Postfix (m (a -> a))
```

the type synonym `OperatorTable`

:

`type OperatorTable m a = [[Operator m a]]`

and a function to convert an `OperatorTable`

into a parser:

```
buildExpressionParser :: forall m a. (Parsing m, Applicative m)
=> OperatorTable m a
-> m a
-> m a
```

The second argument to `buildExpressionParser`

is a parser for everything-but-the-operators.

If we were using this directly, we could make an `Operator`

for each of our operators, like so:

```
addOp :: (Monad m, TokenParsing m)
=> Operator m Term
addOp = Infix parseAdd AssocLeft
where
parseAdd = TmAdd <$ symbol "+"
```

Once we had all of these defined, we’d put them into a list of lists, where the inner lists contained operators with the same precedence and the outer list is sorted in order of descending precedence:

```
opTable :: (Monad m, TokenParsing m)
=> OperatorTable m Term
opTable = [
[expOp]
, [mulOp]
, [addOp, subOp]
]
```

Then all we’d need to do is to stitch everything together with `buildExpressionParser`

:

```
parseTerm =
buildExpressionParser table parseNotAnOperator <?> "term"
where
parseNotAnOperator = parseTmInt <|> parens parseTerm
```

The expression parser doesn’t deal with parentheses, so we handle those in the other case. This will handle things like “(2 + 3) * 5” along with “(((5)))”. The latter has no operators in it, so the expression parser keeps falling through to `parseNotAnOperator`

until the parentheses are dealt with.

We’re going to shuffle some things around a little. Most of the payoff will be in being able to continue to work with a list of parsing rules that we can combine, but it’ll also keep the door open for adding user-defined operators with precedence annotations later on.

We start with a datatype capture associativity and precedence:

```
data OperatorInfo =
OperatorInfo {
assoc :: Assoc
, precedence :: Int
} deriving (Eq, Ord, Show)
```

We then create a datatype for our parsing rules:

```
data ParseRule m t =
ParseRegular (m t)
| ParseOp OperatorInfo (m (t -> t -> t))
```

The `ParseRegular`

rules are for the all of the parsers that we’ve seen so far. The `ParseOp`

rule combines the operator information with the parser for the operator, in the style needed by `Text.Parser.Expression`

.

At some point we’re going to need to partition these two types of rules.

For regular parsers, we just gather the parsers into a `Maybe`

:

```
gatherRegular :: ParseRule m t
-> Maybe (m t)
gatherRegular (ParseRegular p) =
Just p
gatherRegular _ =
Nothing
```

so that we can use `mapMaybe`

over the rules to get hold of the regular rules on their own.

For the operator parsers, we build a singleton `OperatorTable`

, and pad it out using the precedence information:

```
gatherOp :: ParseRule m t
-> Maybe (OperatorTable m t)
gatherOp (ParseOp (OperatorInfo assoc prec) p) =
Just $ [Infix p assoc] : replicate prec []
gatherOp _ =
Nothing
```

If we are using `gatherOp`

to partition parsing rules, we’ll end up with a list of `OperatorTable`

s, so we need a way to combine them:

```
combineTables :: [OperatorTable m a]
-> OperatorTable m a
combineTables os =
foldr (zipWith (++) . pad) (pad []) os
where
-- find the operator with the highest precedence
l = maximum . map length $ os
-- pad everything out to that precedence
pad ls = replicate (l - length ls) [] ++ ls
```

We should stop and look a bit more closely at the above functions. The look like they’re doing what they’re meant to, but there are a few uses of `replicate`

, `length`

and `++`

as well as the use of `maximum`

.

The odds are good that we’re doing more work than we need to. I’m fine with that for a blog post that isn’t about performance, as long as the meaning and correctness of the function is clear - however that isn’t necessarily a zero-sum game.

Let’s have another go.

For a particular rule, the information in the `OperatorTable`

is made up of the precedence, the associativity and the parser. The associativity and the parser get combined into an `Operator`

, so we’ll do that in `gatherOp`

:

```
gatherOp :: ParseRule m t
-> Maybe (Int, Operator m t)
gatherOp (ParseOp (OperatorInfo assoc prec) p) =
Just (prec, Infix p assoc)
gatherOp _ =
Nothing
```

The `OperatorTable`

is a list of list of `Operators`

, where the `Operator`

s in the inner lists all have the same precedence, and the outer list is sorted by descending order of the precedence.

Our function signature is:

```
createTable :: [(Int, Operator m a)]
-> OperatorTable m a
createTable os =
...
```

We can sort the our list by descending precendence by using `sortOn`

from `Data.List`

. We’ll also use `Down`

from `Data.Ord`

to get a descending sort.

So far we have:

```
import Data.List (sortOn)
import Data.Ord (Down(..))
createTable :: [(Int, Operator m a)]
-> OperatorTable m a
createTable os =
... .
sortOn (Down . fst) $
os
```

Before `sortOn`

was added we would have had to have used `sortBy (comparing (Down . fst))`

. The behaviour is the same, but `sortOn`

is faster.

Now we need to group the entries with the same precedence.

We can use `groupBy`

for this. It groups adjacent elements using a custom equality test. The adjacency is handled already because we just sorted the list, so we need to check for equal precedences. We can use `on`

from `Data.Function`

to help with this.

We’re almost there:

```
import Data.List (sortBy, groupBy)
import Data.Ord (comparing, Down(..))
import Data.Function (on)
createTable :: [(Int, Operator m a)]
-> OperatorTable m a
createTable os =
... .
-- we now have [[(Int, Operator m a)]]
groupBy ((==) `on` fst) .
sortBy (comparing (Down . fst)) $
os
```

We can now get rid of the precedence information and eta-reduce the function:

```
createTable :: [(Int, Operator m a)]
-> OperatorTable m a
createTable =
fmap (fmap snd) .
groupBy ((==) `on` fst) .
sortBy (comparing (Down . fst))
```

and we have a snazy little pipeline that doesn’t look to be doing too much in the way of redundant work.

If you haven’t had a browse through the `base`

package in a while, it’s worth doing every now and then - there’s lots of neat stuff in there, and if it’s been a while you might spot some new functions (or new uses for old and familiar functions).

Now we can use all of the above to build a parser from the rules.

We gather the regular parsers together into a parser for non-operators, we gather the operator parsers into an `OperatorTable`

, and we use `buildExpressionParser`

to combine them:

```
mkParser :: TokenParsing m
=> [ParseRule m t]
-> m t
mkParser rules =
let
parseRegular =
(<|> parens parseTerm) .
asum .
mapMaybe gatherRegular $
rules
tables =
createTable .
mapMaybe gatherOp $
rules
parseTerm =
buildExpressionParser tables parseRegular
in
parseTerm
```

We’re still working with a token parser, so we’ll define a style for operators:

```
operatorStyle :: TokenParsing m
=> IdentifierStyle m
operatorStyle =
IdentifierStyle {
_styleName = "operator"
, _styleStart = _styleLetter operatorStyle
, _styleLetter = oneOf ":!#$%&*+./<=>?@\\^|-~"
, _styleReserved = HS.fromList reservedOperators
, _styleHighlight = Operator
, _styleReservedHighlight = ReservedOperator
}
where
reservedOperators =
["+", "-", "*", "^"]
```

and our usual helper function:

```
reservedOperator :: (Monad m, TokenParsing m)
=> String
-> m ()
reservedOperator =
reserve operatorStyle
```

From there, we define a parse for integer literals:

```
parseTmInt :: (Monad m, TokenParsing m)
=> m Term
parseTmInt =
(TmInt . fromInteger) <$> integer <?> "Int"
```

and parsers for all of operators:

```
parseTmAdd :: (Monad m, TokenParsing m)
=> m (Term -> Term -> Term)
parseTmAdd =
TmAdd <$ reservedOperator "+" <?> "+"
parseTmSub :: (Monad m, TokenParsing m)
=> m (Term -> Term -> Term)
parseTmSub =
TmSub <$ reservedOperator "-" <?> "-"
parseTmMul :: (Monad m, TokenParsing m)
=> m (Term -> Term -> Term)
parseTmMul =
TmMul <$ reservedOperator "*" <?> "*"
parseTmExp :: (Monad m, TokenParsing m)
=> m (Term -> Term -> Term)
parseTmExp =
TmExp <$ reservedOperator "^" <?> "^"
```

We package these up into our new `ParseRule`

data structure:

```
parseTermRules :: (Monad m, TokenParsing m)
=> [ParseRule m Term]
parseTermRules =
[ ParseRegular parseTmInt
, ParseOp (OperatorInfo AssocLeft 6) parseTmAdd
, ParseOp (OperatorInfo AssocLeft 6) parseTmSub
, ParseOp (OperatorInfo AssocLeft 7) parseTmMul
, ParseOp (OperatorInfo AssocRight 8) parseTmExp
]
```

with precedence and associativity information stolen from Haskell, and then we build our parser:

```
parseTerm :: (Monad m, TokenParsing m)
=> m Term
parseTerm =
mkParser parseTermRules <?> "term"
```

We than have the option of smothering this in doctests.

I’d be a little more cautious with that recommendation if I thought there was going to be a high rate of churn for these examples. We won’t be expecting anything that parses now to stop parsing when we add orthogonal features to our language, so we won’t have to update these examples unless we’ve broken something.

With that in mind, lets go nuts:

```
-- | The parser for terms of the I language.
--
-- This function is built from the contents of 'parseTermRules',
-- with added support for parentheses.
--
-- We can parse all of the simple forms of the terms:
-- >>> parse parseTerm "3"
-- Success (TmInt 3)
--
-- >>> parse parseTerm "2 + 5"
-- Success (TmAdd (TmInt 2) (TmInt 5))
--
-- >>> parse parseTerm "2 - 5"
-- Success (TmSub (TmInt 2) (TmInt 5))
--
-- >>> parse parseTerm "2 * 5"
-- Success (TmMul (TmInt 2) (TmInt 5))
--
-- >>> parse parseTerm "2 ^ 5"
-- Success (TmExp (TmInt 2) (TmInt 5))
--
-- The left associative operators group to the left:
-- >>> parse parseTerm "3 - 2 + 5"
-- Success (TmAdd (TmSub (TmInt 3) (TmInt 2)) (TmInt 5))
--
-- So brackets grouping things on the left are redundant:
-- >>> parse parseTerm "(3 - 2) + 5"
-- Success (TmAdd (TmSub (TmInt 3) (TmInt 2)) (TmInt 5))
--
-- We need brackets if we want to group things on the right:
-- >>> parse parseTerm "3 - (2 + 5)"
-- Success (TmSub (TmInt 3) (TmAdd (TmInt 2) (TmInt 5)))
--
-- The right associative operator groups to the right:
-- >>> parse parseTerm "3 ^ 2 ^ 5"
-- Success (TmExp (TmInt 3) (TmExp (TmInt 2) (TmInt 5)))
--
-- So brackets grouping things on the right are redundant:
-- >>> parse parseTerm "3 ^ (2 ^ 5)"
-- Success (TmExp (TmInt 3) (TmExp (TmInt 2) (TmInt 5)))
--
-- We need brackets if we want to group things on the left:
-- >>> parse parseTerm "(3 ^ 2) ^ 5"
-- Success (TmExp (TmExp (TmInt 3) (TmInt 2)) (TmInt 5))
--
-- Multiplication binds more tightly than addition:
-- >>> parse parseTerm "3 * 2 + 5"
-- Success (TmAdd (TmMul (TmInt 3) (TmInt 2)) (TmInt 5))
--
-- >>> parse parseTerm "3 + 2 * 5"
-- Success (TmAdd (TmInt 3) (TmMul (TmInt 2) (TmInt 5)))
--
-- So we need to use brackets multiply by the sum of two terms:
-- >>> parse parseTerm "3 * (2 + 5)"
-- Success (TmMul (TmInt 3) (TmAdd (TmInt 2) (TmInt 5)))
--
-- >>> parse parseTerm "(3 + 2) * 5"
-- Success (TmMul (TmAdd (TmInt 3) (TmInt 2)) (TmInt 5))
--
-- Exponentiation binds more tightly than multiplication:
-- >>> parse parseTerm "3 ^ 2 * 5"
-- Success (TmMul (TmExp (TmInt 3) (TmInt 2)) (TmInt 5))
--
-- >>> parse parseTerm "3 * 2 ^ 5"
-- Success (TmMul (TmInt 3) (TmExp (TmInt 2) (TmInt 5)))
--
-- So we need to use brackets if the exponent or the power is a product (or sum) of two terms:
-- >>> parse parseTerm "3 ^ (2 * 5)"
-- Success (TmExp (TmInt 3) (TmMul (TmInt 2) (TmInt 5)))
--
-- >>> parse parseTerm "(3 * 2) ^ 5"
-- Success (TmExp (TmMul (TmInt 3) (TmInt 2)) (TmInt 5))
--
-- Nonsense is still rejected:
-- >>> parse parseTerm "potato"
-- Failure (interactive):1:1: error: expected: term
-- potato<EOF>
-- ^
--
-- Even if you try to hide it in brackets:
-- >>> parse parseTerm "((potato))"
-- Failure (interactive):1:3: error: expected: operator
-- ((potato))<EOF>
-- ^
parseTerm :: (Monad m, TokenParsing m)
=> m Term
parseTerm =
mkParser parseTermRules <?> "term"
```

When it comes to pretty printing, we’d really like to be able to print out our terms - so that we can parse them back in again and get the original term - with the minimal number of brackets required

In fact, we should be able to flip our doc tests from the parse around and get the same results, except not including the results with redundant brackets or potatoes.

That’s what we’ll work towards.

We’re going to create a data type for pretty printing rules, and it’s going to be similar to `ParseRule`

from earlier:

```
data PrettyRule a =
PrettyRegular (a -> Maybe Doc)
| PrettyOp OperatorInfo (a -> Maybe (a, a)) (Doc -> Doc -> Doc)
```

The `PrettyRegular`

variant is for everything other than operators, and it pretty prints `a`

if the rule matches. The `PrettyOp`

variant is for the operators, and includes the precedence and associativity information for the operator, a matching function tat returns the arguments to the operator, and a pretty printing function for the operator that requires that both arguments are pretty printed before it is used.

We’ll be dealing with the regular and operator pretty printers separately, so again we’ll need functions to partition the two types of rules.

For the regular pretty printing rules, we gather the pretty printers into a `Maybe`

:

```
gatherRegular :: a
-> PrettyRule a
-> Maybe Doc
gatherRegular t (PrettyRegular f) =
f t
gatherRegular _ _ =
Nothing
```

so that we can use `mapMaybe`

over the rules to get hold of the regular rules on their own.

The pretty printing rules for operators require a couple of helper functions.

We need a function that will search through the rules to find the `OperatorInfo`

for a term, if it exists:

```
findOperatorInfo :: [PrettyRule a]
-> a
-> Maybe OperatorInfo
findOperatorInfo rules tm =
asum .
fmap (checkOperatorInfo tm) $
rules
where
checkOperatorInfo t (PrettyOp info match _) =
info <$ match t
checkOperatorInfo _ _ =
Nothing
```

We also need a function to determine if the argument to an operator needs parentheses.

First we add a data type to describe the arguments to an operator:

```
data Argument =
ArgumentLeft
| ArgumentRight
deriving (Eq, Ord, Show)
```

and a function that works out if an argument is in the right position for it to associate:

```
argumentAssociates :: Argument
-> Assoc
-> Bool
argumentAssociates ArgumentLeft AssocLeft =
True
argumentAssociates ArgumentRight AssocRight =
True
argumentAssociates _ _ =
False
```

We could have used `Assoc`

here and not written `Argument`

, but new data types and functions are cheap and I didn’t want to leave the door open to passing in `AssocNone`

by accident in the future and getting unexpected results.

Now we’re ready for the `needsParens`

function:

```
needsParens :: Argument
-> OperatorInfo
-> Maybe OperatorInfo
-> Bool
```

which takes the position of the argument to an operator, the operator information for the current operator and for the argument to the operator - the result of `findOperatorInfo`

- and returns a Boolean indicating whether we should add parentheses or not.

If the argument wasn’t an operator, then we don’t need parentheses:

```
needsParens _ _ Nothing =
False
```

Otherwise, based on some playing around with doctests, we need parentheses if:

`needsParens arg info (Just argInfo) =`

the operator has no associativity,

` assoc info == AssocNone ||`

the argument has a lower precedence than the current operator,

` precedence argInfo < precedence info ||`

or if the argument has the same precedence as the current operator but the argument is not in the right position for it to associate

` (precedence argInfo == precedence info && not (argumentAssociates arg (assoc info)))`

Now we can prepare a pretty printer for operators.

Thanks to the laziness of Haskell, we can pass in the pretty printer for terms that we’re trying to build. We also pass in the result of `findOperatorInfo`

.

```
gatherOp :: a
-> (a -> Doc)
-> (a -> Maybe OperatorInfo)
-> PrettyRule a
-> Maybe Doc
```

If we’re given an operator, we check to see if it matches the rule we’re currently processing:

```
gatherOp t pretty findInfo (PrettyOp i match printer) = do
(t1, t2) <- match t
```

We then build little functions that will add parentheses if they are needed:

```
let addParens b =
if b then parens else id
let p1 =
addParens .
needsParens ArgumentLeft i .
findInfo $
t1
let p2 =
addParens .
needsParens ArgumentRight i .
findInfo $
t2
```

and use them in conjunction with our overall pretty printer and the function that does the printing for the current operator:

` return $ printer (p1 . pretty $ t1) (p2 . pretty $ t2)`

In the case whre we’re not given an operator, this function isn’t for us:

```
gatherOp _ _ _ _ =
Nothing
```

We can use the above functions to build a pretty printing function from the pretty printing rules:

```
mkPretty :: [PrettyRule a]
-> a
-> Doc
mkPretty rules =
let
prettyRegular tm =
asum .
fmap (gatherRegular tm) $
rules
findInfo =
findOperatorInfo rules
prettyOp tm =
asum .
fmap (gatherOp tm prettyTerm findInfo) $
rules
prettyTerm tm =
fromMaybe (text "???") .
asum .
fmap ($ tm) $
[ prettyRegular , prettyOp ]
in
prettyTerm
```

We’re building the pretty printer out of the pretty printers for regular terms and the pretty printer for operators. The pretty printer for regular terms shouldn’t be too surprising at this point.

The only trick with the pretty printer for the operators is that we assemble some functions and pass them in to `gatherOp`

, so that we don’t need to pass in all the rules.

That should be all we need, so it’s time to press on.

The heavy lifting has already been done, so we just need to put together the pieces for *I*.

We have one fairly usually looking pretty printer for `TmInt`

, making use of the `int`

helper from `ansi-wl-pprint`

:

```
import Text.Parser.Token.Highlight (Highlight (..))
import Text.Trifecta.Highlight (withHighlight)
prettyTmInt :: Term
-> Maybe Doc
prettyTmInt (TmInt i) =
Just $ withHighlight Number (int i)
prettyTmInt _ =
Nothing
```

We’re stealing the highlighting from `trifecta`

again, since the parser we use for `TmInt`

adds the `Number`

highlight under the hood.

We also have a pair of functions for each of our operators. They’re all pretty similar in this instance, so we’ll only look at the code associated with `TmAdd`

.

One of the functions checks that we’re dealing with the operator we’re interested in, and returns the arguments to the operator if that’s the case:

```
-- |
matchTmAdd :: Term
-> Maybe (Term, Term)
matchTmAdd (TmAdd tm1 tm2) =
Just (tm1, tm2)
matchTmAdd _ =
Nothing
The other functions pretty prints the operator, given that the arguments to the operator have already been pretty pritned:
-- |
prettyTmAdd :: Doc
-> Doc
-> Doc
prettyTmAdd d1 d2 =
d1 <+> reservedOperator "+" <+> d2
```

Once we have these functions, we can build a list of the pretty printing rules with the same operator information that we used for the parser:

```
prettyTermRules :: [PrettyRule Term]
prettyTermRules =
[ PrettyRegular prettyTmInt
, PrettyOp (OperatorInfo AssocLeft 6) matchTmAdd prettyTmAdd
, PrettyOp (OperatorInfo AssocLeft 6) matchTmSub prettyTmSub
, PrettyOp (OperatorInfo AssocLeft 7) matchTmMul prettyTmMul
, PrettyOp (OperatorInfo AssocRight 8) matchTmExp prettyTmExp
]
```

Then we combine them all into a pretty printing function:

```
prettyTerm :: Term
-> Doc
prettyTerm =
mkPretty prettyTermRules
```

As usual, we then spray doctests all over the place:

```
-- | The pretty printer for terms of the I language.
--
-- This function is built from the contents of 'prettyTermRules'.
-- It will print "???" if none of the rules apply - which should never happen.
--
-- We can print all of the simple forms of the terms:
-- >>> render 0.5 40 prettyTerm (TmInt 3)
-- 3
--
-- >>> render 0.5 40 prettyTerm (TmAdd (TmInt 2) (TmInt 5))
-- 2 + 5
--
-- >>> render 0.5 40 prettyTerm (TmSub (TmInt 2) (TmInt 5))
-- 2 - 5
--
-- >>> render 0.5 40 prettyTerm (TmMul (TmInt 2) (TmInt 5))
-- 2 * 5
--
-- >>> render 0.5 40 prettyTerm (TmExp (TmInt 2) (TmInt 5))
-- 2 ^ 5
--
-- The left associative operators don't need extra brackets when things are grouped to the left:
-- >>> render 0.5 40 prettyTerm (TmAdd (TmAdd (TmInt 3) (TmInt 2)) (TmInt 5))
-- 3 + 2 + 5
--
-- But they do need brackets when the grouping branches to the right:
-- >>> render 0.5 40 prettyTerm (TmAdd (TmInt 3) (TmAdd (TmInt 2) (TmInt 5)))
-- 3 + (2 + 5)
--
-- The right associative operators don't need extra brackets when things are grouped to the right:
-- >>> render 0.5 40 prettyTerm (TmExp (TmInt 3) (TmExp (TmInt 2) (TmInt 5)))
-- 3 ^ 2 ^ 5
--
-- But they do need brackets when the grouping branches to the left:
-- >>> render 0.5 40 prettyTerm (TmExp (TmExp (TmInt 3) (TmInt 2)) (TmInt 5))
-- (3 ^ 2) ^ 5
--
-- Multiplication binds more tightly than addition, so we don't want any brackets if we multiply and then add:
-- >>> render 0.5 40 prettyTerm (TmAdd (TmMul (TmInt 3) (TmInt 2)) (TmInt 5))
-- 3 * 2 + 5
--
-- If we are adding and then multiplying, we'll want brackets:
-- >>> render 0.5 40 prettyTerm (TmMul (TmInt 3) (TmAdd (TmInt 2) (TmInt 5)))
-- 3 * (2 + 5)
--
-- This is true regardless of whether the addition is on the left or the right:
-- >>> render 0.5 40 prettyTerm (TmAdd (TmInt 3) (TmMul (TmInt 2) (TmInt 5)))
-- 3 + 2 * 5
--
-- >>> render 0.5 40 prettyTerm (TmMul (TmAdd (TmInt 3) (TmInt 2)) (TmInt 5))
-- (3 + 2) * 5
--
-- Exponentiation binds more tightly than multiplication, so we don't want any brackets if exponentiate and then multiply:
-- >>> render 0.5 40 prettyTerm (TmMul (TmExp (TmInt 3) (TmInt 2)) (TmInt 5))
-- 3 ^ 2 * 5
--
-- If we're multiplying and then exponentiating, we'll want brackets:
-- >>> render 0.5 40 prettyTerm (TmExp (TmInt 3) (TmMul (TmInt 2) (TmInt 5)))
-- 3 ^ (2 * 5)
prettyTerm :: Term
-> Doc
prettyTerm =
mkPretty prettyTermRules
```

As was the case with *N*, we don’t need to change the tests or the REPL, and they both continue to work.

It’s always worth having a play around in the REPL at this point, as something somewhere between a double-check and a victory lap:

```
> 2 + 3 * 5
2 + 3 * 5 ==> 17
> (2 + 3) * 5
(2 + 3) * 5 ==> 25
> 1 + 2 + 3
1 + 2 + 3 ==> 6
> 1 + (2 + 3)
1 + (2 + 3) ==> 6
```

There’s a problem though - `needsParens`

is incorrect.

A post on reddit covered some similar ground, but with different code for the equivalent of `needsParens`

.

Clearly it’s time for more `QuickCheck`

in order to sort out what is going on.

- Add the logic operators for conjunction and disjunction to
*B*, using`&&`

and`||`

as infix operators with the same precedence and associativity as reported by GHCi with`:i (&&)`

etc…- Make sure that the semantics cause them to short-circuit appropriately.

- Add the logic operator for negation to
*B*using`~`

as a prefix operator.

Site proudly generated by Hakyll

This work is licensed under a Creative Commons Attribution 4.0 International License