This is an idea for how to case on a byte sequence without any special support from the compiler.
The basic idea is explored in https://github.com/layer-3-communications/fortios-syslog, but there
code generation is used rather than TemplateHaskell
. The idea is that, at compile time, generating
a perfect hash function for all strings of the same length is a good way to pattern match on a sequence of
bytes. Roughly, we have:
foo :: Bytes -> Bar
foo b = case B.length b of
3 -> case hash3 b of
Car | b == "car" -> ...
Zoo | b == "zoo" -> ...
_ -> ...
5 -> case hash5 b of
Zepto | b == "zepto" -> ...
Knuth | b == "knuth" -> ...
Allow | b == "allow" -> ...
_ -> ...
_ -> ...
Above, Car
, Zoo
, etc. are pattern synonyms for integers. Using the integers directly would
be terrible and would obscure things. Even the above strategy is a bit verbose. Preferrable
would be:
foo :: Bytes -> Bar
foo b = case b of
Car -> ...
Zoo -> ...
Zepto -> ...
...
_ -> ...
With a unidirectional pattern synonym, this can be done. The check for the length, the computation of
the hash, the casing on the hash, and the check against the literal all get rolled in there together.
I believe that GHC might optimize this to do things in the same order as above (we definitely want
the check against the literal to happen last), but I need to confirm that. In the second strategy,
the patterns need to be generated, and TemplateHaskell
would be a good way to generate them.
Perhaps something like:
makeBytesPatterns ["car","zoo","zepto",...]
Probably sitting in its own module, could generate patterns like:
pattern Car :: Bytes
pattern Car <- ((\x -> B.length x == 3 && hash3 x == 26938246 && equals3 x 'c' 'a' 'r') -> True)