lower-case haskell
Type-level trickery, UpperCase Haskell, is the showy rock-star of Haskell and all power to the Type. I like a good Type as much as anyone, but what keeps me using the language is the work-a-day value-level coding tool-kit - lower case haskell.
Let me explain.
In noodling around with stan, a wonderful tool developed with obvious care and attention by the talented kowainik troupe, I created hcount, a cheap and cheerful attempt to count symbol usage in Haskell projects. Please try it out and let me know how it runs.
Here’s my personal top 10:
base $ 828
local x 696
base fromLabel 613
base . 536
local a 510
numhask * 510
base & 499
lens .~ 483
base <$> 479
base <> 422
You can tell so much about a coder from their top 10 list. I’m an
unreconstructed $
user after a decade of Haskell hacking: you write
from right to left, plonk down a $
, add the next thing, think some
more, rinse and repeat. I use <$>
extensively because of this style: I
plonk down a $
and then realise I am moving up a functorial level so
wrap it to a <$>
.
The very next refactor I have planned, however, is to replace each and
every <$>
with fmap
. The flow I get from subbing <$>
for $
is
almost always interrupted with having to then bracket everything on the
right because of the change in fixity.
I have made some efforts to move to a more dotty approach and I suspect
my .
usage has risen of late. I also, somewhat unusually I suspect,
commonly write left to right using &
, the flipped $
, especially when
using lens. But I never use >>>
(the flipped .
) probably because of
it’s garish look.
I’m an unrepentant user of
OverloadedLabels,
hence the fromLabel
(which ghc inserts on my behalf) and .~
usage. I
tend towards single letter, anonymous local names and hope that my logic
function blocks and Typing habits are simple enough to justify this
(they’re probably not). I’m a mathy coder given *
is right up there
(+
is 14th). I love semigroup and look for it constantly.
lower-case haskell
base fromLabel 613
base pure 200
numhask zero 176
base mempty 173
numhask one 168
protolude show 143
numhask fromIntegral 140
protolude bool 105
base fmap 97
base maybe 90
Once I filtered out the operators I was immediately struck by just how
much love and respect I have for lower case haskell (except for
fromIntegral
which always grates given how loooong and boringly
obvious it is). Why the love?
pure
I came late to the Haskell party, started up the typical learning curve of Monoid-Functor-Applicative-Monad and never quite made it to the top. Monads (or MoNaDs as they used to spell it) are just so 90s, so late Gen X - early Millennial. They remind me of that other 90s sickly-child that is Radiohead; if they saved rock and roll (twice!) then how come you can’t dance to it?
Wading through other peoples’ library monad wrappers (“i wanna have
control”), part of the mess was the metaphysical-linguistic confusion of
return
. Where am I that I have to now return, I would ask (“i don’t
belong here”). The tutorials told me I was somewhere (“what the hell am
i doing here”), somewhere special (“i wish i was special”), but now I
need to go back to somewhere else (“but I’m a creeeeep!”).
Don’t @ me on this; I understand monads. I hate on ’em cause I don’t like ’em.
Looking through my lower-range usage, I have one use of =<<
, one of
>=>
, a single >>=
and can’t recall the signature for any of them.
Meanwhile pure
means simple; it’s only a value, gently lifted and
placed into an Applicative. Rock on AMP!
bool
When I first learnt about this gem, this functional eliminator, I had an
epiphany: if I used bool
I would never ever again have to decide how
to indent an if
-then
-else
statement. I’d never even have to recall
whether haskell has an else clause (of course it does because without it
it’s a partial but I’m forgetful and often dull).
Most of all, what happens when I code with bool
is I stay in the flow
compared with having to use my if
-then
-else
fingers or construct a
case
-blah
-of
-x->etc
monolith. I switch from homo categoralis,
master of domain and codomain alike, to homo narratavis, teller of
post-modern stories and (sometimes) bender of truthiness.
A nice extra of bool
usage is cheap refactoring. You can cut and paste
a bool statement, no matter the complexity, and stand a very good chance
it will work in a new context, without moving brackets or indents or
redesigning a case statement.
The only problem is nested bool
’s start to get a bit hairy (though not
as bad as nested if-then-else’s). More than 2 layers of bool and it’s
time for guards.
maybe & either
It took me years to discover maybe
and either
. I knew they were
there but I didn’t naturally reach for them. And then one day, neck deep
in some transformer stack, looking up whether I needed to run my
runEitherT before or after I execute my execMaybe, deleted the whole
stack in anger and never looked back.
Consider: | |
---|---|
throwing | maybe (throw SomeException) |
defaulting | either (const def) |
maybeing | either (const Nothing) Just |
eithering | maybe (Left “error”) Right |
there is an economy of compositional movement you don’t get anywhere else.
zero & one
In plain Haskell, there is no zero
and there is no one
- our
ancestors weep at the loss. Sure we have the literate 0
& 1
which
desugars to fromInteger 0
& fromInteger 1
but these are pale shadows
of the twin unital gods of arithmetic.
Consider these examples:
-- what signum would look like if we had a zero and one
sign = bool (negate one) one . (>= zero)
-- conversion of boolean to a number to construct an identity matrix
ident = tabulate (bool zero one . isDiag)
-- applicative standard deviation
(\ss s -> (ss - s ** (one + one)) ** (one / (one + one))) <$> (** (one + one)) <*> id
-- boolean conversion to a number
bool zero one
In each, there is a sense of intent, of unital usage to shift domain,
rather than the just another magical number feel that comes from using
0
or 1
.
first & second
Another pair of terse, vital tools in my kit that are not even in prelude. They were trapped in the unfortunate arrows abstraction for a long while but find a nice home in bifunctors.
Consider adding commas to a number and fixing the decimal points:
addcomma n x =
uncurry (<>) .
second (take n) .
first (reverse . intercalate "," . chunksOf 3 . reverse) .
Text.breakOn "." $
x
How do you write that without first
& second
(or bimap)? Only by
busting the composition into components and exponentiating complexity.
Amazingly and somewhat mysteriously, they work with both tuples and Either, so you can refactor between the two.
I track two things at once so much in my code that
<<*>>
,
the biapplicative version of spaceship,is in my top 20. I bet others do
too.
fmap
For the most quint-essential function in all of haskelldom, fmap
has
the worst documented explanation. It starts with reference to
-XApplicativeDo (scoring 0% proliferation on a recent
GHC2021
post), sugars the very definition being explained into do notation, and
then talks self-referentially about an implied Functor constraint, when
fmap is the sole operator of Functor. Never get a committee of fish to
explain water.
But it’s the best named, especially in comparison to the other maps in
the other languages. This is the functor-map (personally, I pronounce it
f'n'map
) because, unlike where you’re from, we have other ways to map.
There is the
bimap
of bifunctors, the
dimap
of profunctors (with lmap and rmap), the hippy
contramap,
to say nothing of the various monomorphic maps such as
Map.map.
And when others say map we instead often see traversing, lifting,
sequencing, aligning or zipping. Haskell has 50 different names for
mapping.
const & id
I was surprised that neither id
nor const
made the top 10 (11th and
24th). I think that are more common in early code but get eventually
factored out as polish occurs (eg maybe def id becomes
fromMaybe def). These two, and their upper class cousins Identity
and Const
are what’s most noticeably missing in other
language constructs.
A prediction
There’s so much more I could that can be said from this simple, cheesy
analysis. For instance, my heavy usage of reverse
(19th most common)
is no doubt a code smell. I get lazy and use lists where I should be
using
Seq.
Instead, I’ll hazard an adjunctive prediction. Starting from a position of terrible, Haskell tooling has now moved into a zone of getting there. With the gap between ghc hacking and front-line tools having considerably shrunk, so much so that I can interface with the beast, then expect the Haskell user interface to evolve at speed.
And this may be a catalyst for ubiquitous adoption,, lower case haskell may yet have it’s day to shine. If Haskell begins to turn GHC towards it’s own behaviours and standards, and the community starts to apply it’s categorical sharpness on the problem domain of software development. Watch this space and remember to upgrade your GUI’s!