I think what you wrote is pretty readable, especially if you already know what a trace is.
The biggest downside in my opinion is how fragile it is if it receives anything other than a square matrix. Since there’s nothing guaranteeing that the input is square in the types, you’re entirely on your own for preventing run-time errors. It’s up to you how much you care about this—maybe this function is only used in highly controlled circumstances and you prefer this style over alternatives.
But if you wanted a more robust alternative, I’d probably do something like this:
{-# LANGUAGE BlockArguments, LambdaCase #-}
import Data.List (unfoldr)
tailMay = \case
_ : t -> Just t
_ -> Nothing
traceM = sum . unfoldr \case
(h : _) : t -> (h, ) <$> traverse tailMay t
_ -> Nothing
(The semantics of this implementation on non-square inputs are that it takes the trace of the square matrix found by taking the least entirely filled dimension in both directions, if that makes sense. Give it a ragged input like [[0, 1, 2], [3, 4, 5, 6], [7, 8, 9], [10, 11]]
, and it computes the trace of [[0, 1], [3, 4]]
, since the fourth row has length 2.)