Without changing the code too drastically I see two immediate improvements, for one you can pull out the `Left`

constructor in front of the call to fail, thus keeping `Right`

and `Left`

on the same level and making the distinction clearer:

```
if areSimilarVectors sa sb
then Right $ dot' a b
else Left $ failed sa sb
```

Secondly because the code handles failure cases I’d suggest you reverse the predicates for the ifs to reduce the distance between Reason <-> Error

```
if not $ isVector sa sb then
VectorsExpected
else
if not $ isSameSize sa sb then
InvalidVectorSize ay by
else
UnexpectedError
```

Other things you might want to consider is the naming of `isVector`

and the amount of shadowing of `sa`

and `sb`

. Why are you using a where clause and then still pass the arguments around explicitly? Maybe it would be even better if you matched the `Tuple`

s in the toplevel pattern match and stopped using `fst`

and `snd`

alltogether? Applying that we get to:

```
dot :: Matrix -> Matrix -> Either MatrixError Number
dot (Matrix a (Tuple ax ay)) (Matrix b (Tuple bx by)) =
if areSimilarVectors then Right $ dot' a b else Left failed
where
dot' a b = _dot (join a) (join b)
areSimilarVectors = areVectors && areSameSize
areVectors = ax == 1 && bx == 1
areSameSize = ay == by
failed =
if not areVectors then
VectorsExpected
else
if not areSameSize then
InvalidVectorSize ay by
else
UnexpectedError
```

I would also look at using guards:

```
dotProduct :: Num a => [a] -> [a] -> Either String a
dotProduct as bs
| notSimilar = Left "bad operands"
| otherwise = Right $ sum $ zipWith (*) as bs
where notSimilar = length as /= length bs
```

Another option is to use `throwError`

:

```
import Control.Monad
import Control.Monad.Except
dotProduct'' :: Num a => [a] -> [a] -> Either String a
dotProduct'' as bs = do
when (length as /= length bs) $ throwError "bad operands"
return $ sum $ zipWith (*) as bs
```

You’re forcing the reader to read the code linearly, but it also removes a level of indentation.

The Either monad allows you to use do notation here:

```
dot :: Matrix -> Matrix -> Either MatrixError Number
dot (Matrix a (Tuple ax ay)) (Matrix b (Tuple bx by)) = do
unless (ax == 1 && bx == 1) $ Left VectorsExpected
unless (ay == by) $ Left $ InvalidVectorSize ay by
Right $ _dot (join a) (join b)
```