By: Tom Sydney Kerckhove <syd@cs-syd.eu>
Report <> used to concatenate strings or text
A sentence built out of <> hides its own shape. Whether there is a space
between two pieces is something a reader counts rather than reads, since
every space is a literal operand of its own, and a piece added later is
another operator in the right place rather than another element. The cost
is not only in the reading: each <> on Text allocates an array of its
own, so a chain builds and throws away every prefix of the result, where
Text.concat over the same pieces sums their lengths and allocates once.
The types tier this rule was filed under does not exist yet, so what
ships is the literal-operand approximation the plan already sanctioned: a
chain of <> with a string literal as one of its operands. That finds 275
sites over five repositories and none in this one, so it lands here as a
guard and as a ratchet for anyone adopting it.
Read off the parse tree rather than the token stream, which is the
opposite of how the other syntactic facts are read and is load-bearing: a
token before an operator is not an operand of it, so `text "a" <> b` has
a string literal beside the <> while concatenating whatever text returns.
That shape is everywhere in the corpus, in chunk, in toHtml, in
textValue, and a token scan would have called all of it a violation.
A chain is one finding rather than one per operator, because two findings
inside one statement cannot both be answered: the second suppression a
reader wrote would be one that suppresses nothing. Parentheses are peeled
before the operands are read, so ("a" <> t) <> "b" is one chain too.
This is the first rule to want a fact extraction did not produce, so it
brings SemigroupChain, the traversal that fills it, and syb for that
traversal: finding every <> in a module means walking the whole
expression tree, and hand-writing that over HsExpr would have been most
of the rule. The operand shapes stay in the fact rather than collapsing
to the answer, so the types tier widens what an operand can be without
changing what the rule decides.
The one class the corpus turns up where the rule asks for something it
should not is the lazy representations. A <> on a Data.Text.Lazy.Builder
or a lazy ByteString appends a chunk rather than copying, so the cost
half of the argument does not hold and the operator is the type's
intended API. Five sites of 275, and what the suppression is for.