aligning characters for fun and profit, with Haskell and PureScript
In this "24 days of…" I want to focus on tricks and patterns that are (at least to some extent):
- expressed using a relatively simple machinery
- capture some idea in a concise, but not necessarily intuitive way
- non-obvious, but can be discovered by accident or when striked with a bright idea
- neat in their own way
- mostly ungooglable
Some of these are widely used in every other codebase, other pop up occasionally in the dark corners. Some are charming, but when used inappropriately can seed confusion and havoc.
Even though the title says "syntactic", I'll steer away from talking about plain usage of syntactic sugar and GHC's syntactic extensions since they are already very well documented in GHC User's Guide and excellent 24 Days of GHC Extensions.
Real witches do not rely on syntactic sugar, they cast thunderstorms of funny operators instead.
Obviously it's a work in progress. I don't yet have a full list of things to write about, which means that the topics won't be ordered very meaningfully. I might start running out of good ideas soon, and would have to pick up lens
.
Guest posts are very welcome, and don't have to be in Haskell/PureScript as long as they fit the idea.
This simple spell allows to conditionally construct an empty or single-element list:
λ> [ 42 | True ]
[42]
λ> [ 42 | False ]
[]
It uses a small subset of the list comprehension syntactic sugar.
Is there any use for it? Bundled with concat
it provides a nice way of constructing
lists where particular elements should be included only if a set of conditions is satisfied:
λ> concat [ [ "foo" | True ], [ "bar" | True, False ], [ "baz" | True ] ]
["foo","baz"]
But what's that good for?
Let's say you want to conditionally add several css classes to an html element (uses lucid):
import Lucid
todoHtml :: Todo -> Html ()
todoHtml todo =
div_
[ classes_ $ concat
[ [ "todo" ]
, [ "completed" | isCompleted todo ]
, [ "active" | isActive todo ]
, [ "urgent" | isActive todo, deadlineSoon todo ]
, [ "wasted" | isActive todo, deadlineMissed todo ]
]
]
(todoTitle todo)
Or you want to list yourself out of boring interview questions:
main :: IO ()
main = mapM_ (putStrLn . fizzbuzz) [1..100]
where
fizzbuzz n = concat (fizz ++ buzz ++ num)
where
fizz = [ "Fizz" | n `mod` 3 == 0 ]
buzz = [ "Buzz" | n `mod` 5 == 0 ]
num = [ show n | null fizz, null buzz ]
As any proper spell this one might be confusing and non-obvious for someone who haven't encountered it before. But when used wisely it provides a good structure and easy to grasp code-pattern which justifies it.
There are many more crazy things that one can do with Haskell's list comprehensions, you can read more about them here. PureScript, on the other hand, choses to not have list comprehension sugar.
In the next section I'll show another pattern that relies on a funky operator and allows to express the same idea in a more generic way.
The <$
operator is provided by both Haskell's and PureScript's Preludes:
(<$) :: Functor f => a -> f b -> f a
The idea is that it takes an f b
and replaces all the b
values in it with the a
that was given to it, resulting in f a
.
It can be easily defined in terms of <$>
(aka fmap
):
a <$ fb = const a <$> fb
And used for all of your favourite functors:
λ> 42 <$ [1, 2, 3]
[42,42,42]
λ> 42 <$ Just 0
Just 42
λ> 42 <$ Nothing
Nothing
λ> 42 <$ Right 0
Right 42
λ> 42 <$ Left "come 40 million years later"
Left "come 40 million years later"
λ> 42 <$ putStrLn "Computing the ultimate answer"
Computing the ultimate answer
42
Until today (when I finally read the docstring) my misintuition about <$
has been something like "evaluates the stuff on the right side, throws away the result and returns the value on the left side".
It's handy for returning the value while logging the act of returning it:
gift <$ putStrLn "HERE'S YOUR GIFT, BOY. HO! HO! HO!"
You can also combine it with do-notation
thus avoiding the pure
/return
in the ond of it:
-- | Gets reindeer ready for action.
initReindeer :: IO ReindeerStatus
initReindeer = ReindeerReady <$ do
putStrLn "Attach the sledges"
putStrLn "Turn the bells on"
putStrLn "Open the stable doors"
I find this particularly handy when implementing "eval" functions for purescript-halogen
:
Toggle next -> next <$ do
state <- H.get
let nextState = not state
H.put nextState
H.raise $ Toggled nextState
Combined with guard
, we can replicate the success of [ a | cond ]
with no sugar:
-- | Returns a gift for a given kid (if they deserve it).
giftFor :: Kid -> Maybe Gift
giftFor kid =
requestedGift kid <$ guard (isGoodKid kid)
Note that the above expression in the body of this function can return a list, Maybe
, IO
(throws an exception for bad kids), or any other type that implements the Alternative
.
One could achieve the same generality using [ a | cond ]
and enabling MonadComprehensions
extension, but I'll leave that to muggles.
<$>
operator is a handy alias for our favourite map
/fmap
whose definition makes up a Functor
typeclass.
It's used a lot in Haskell and PureScript alike. It's an important stepping stone to more elaborate tricks, and a building block for many combinators.
(<$>) :: Functor f => (a -> b) -> f a -> f b
Taking a function (a -> b
) and an f a
functor, fmap
applies that function to every a
inside it.
"Container types" are most intuitive Functor
s, quite literally their fmap
applies the given function to every element that's contained in there.
λ> (+1) <$> Just 2017
Just 2018
λ> fmap (gregorianMonthLength 2017) [1..12]
[31,28,31,30,31,30,31,31,30,31,30,31]
It's also more-or-less intuitive to think about IO
and other effectful actions as something that holds a value:
λ> map Char.toUpper <$> getLine
ho ho ho
"HO HO HO"
However don't be surpised if that intuition doesn't work for some types:
λ> ((+2000) <$> (+18)) 0
2018
To learn more about functors, I recommend reading Typeclassopedia.
As shown above, I prefer using fmap
/map
function when mapping over multi-element data-structures. <$>
shines when applying functions to values in effectful contexts or things like Maybe
or Either
. The second case is more synonymous to function application, whereas the first actually relates to the logic of the program and I prefer to explicitly point out that I map over every element of a structure.
<$>
is quite descriptive and helpful when it's needed to wrap result of some action in a newtype before binding it to a name:
username <- Username <$> getEnv "USERNAME"
password <- Password <$> getEnv "PASSWORD"
Or extracting a part of the value:
today <- utctDay <$> getCurrentTime
<$>
will be popping up quite often in the upcoming chapters. Tomorrow we'll look at it's friend flap
/<@>
and then at some point we'll get to their ally <*>
and see how they all can work together.
While writing the previous posts I noticed that Purescript's Data.Functor
module defines an interesting function called flap
, which is also availabe via operator named <@>
.
(<@>) :: Functor f => f (a -> b) -> a -> f b
Takes a function in a context of f
and applies it to a normal value a
.
Notice that it's a lot like <$>
(fmap
) only now first argument, the a -> b
function is wrapped in an f
functor, while the second argument is just a normal a
value.
Here is how it could be defined in haskell:
flap :: Functor f => f (a -> b) -> a -> f b
flap ff x = (\f -> f x) <$> ff
(<@>) :: Functor f => f (a -> b) -> a -> f b
(<@>) = flap
infixl 4 <@>
Note that flap
generalizes a more well-known flip
function.
See, if we start with the type of flap
:
f (b -> c) -> b -> f c
and substitute a ->
for the f
, we'll get:
(a -> b -> c) -> b -> a -> c
Which is exactly the type of flap
. We can indeed use it as if it was flip
:
λ> flap (++) "foo" "bar"
"barfoo"
Another simple thing we can do is applying several functions to the same value:
λ> [maximum, minimum] <@> [5,7,1,9,3,2,6]
[9,1]
λ> [add 1, add 2, add 3] <@> 0
[1,2,3]
But combining it with <$>
makes it more intersting:
λ> add <$> [1,2,3] <@> 0
[1,2,3]
A neat thing is going on here. We first do add <$> [1,2,3]
which is equivalent to [add 1, add 2, add 3]
.
Then we apply each of those functions to 0
using <@>
operator.
Here's a slightly more useful application of lists:
-- | Every christmas since year 2000th.
christmases :: [Day]
christmases =
fromGregorian <$> [2000..] <@> 12 <@> 25
Works equally well for IO:
thisChristmas :: IO Day
thisChristmas =
fromGregorian <$> getCurrentYear <@> 12 <@> 25
Using f <$> fa <@> b <@> c
pattern we can give function f
a value which is wrapped in a functor along with a couple more normal values. f
can be of any arity, we'll just chain the <@>
.
However we still lack an ability to apply a function to several non-pure values. Next we'll see how it can be done with a help of <*>
.
"Apply over" operator — <*>
, brings the best of <$>
and <@>
together:
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
We stepped up from Functor
to Applicative
, and now both the a -> b
function and a
value are wrapped in the context of f
:
λ> Just not <*> Just False
Just True
λ> Nothing <*> Just False
Nothing
Combining it with <$>
and <@>
allows us to lift functions with arbitrary amount of arguments while still passing some pure values in there:
*Main> (\wish name -> wish ++ ", " ++ name) <$> ["Merry Christmas", "Happy new year"] <*> ["Foo", "Bar"]
["Merry Christmas, Foo","Merry Christmas, Bar","Happy new year, Foo","Happy new year, Bar"]
-- | Every Friday 13th since year 2000th.
evilFridays :: [Day]
evilFridays =
filter isFriday $ fromGregorian <$> [2000..] <*> [1..12] <@> 13
There are many useful applications for Applicative
. Context-free parsers and validators are among my favourite.
Chaining <$>
, <@>
, <*>
is cute, but it gets a bit hard to follow with complex expressions. Fortunately, both Haskell and PureScript now support ApplicativeDo
notation.
The above example could actually be rewritten as:
evilFridays :: [Day]
evilFridays = filter isFriday $ do
year <- [2000..]
month <- [1..12]
pure $ fromGregorian year month 13
Both operators <*
and *>
allow to combine two applicative actions in such a way that both are executed but only one is returned. <*
returns the result of the first, *>
the result of the second.
Both can be defined in terms of <*>
.
For lists, the behaviour might seem a bit strange:
> ["foo", "bar"] <* [1..3]
["foo","foo","foo","bar","bar","bar"]
Every element of the right list is replaced with the left list and the resulting list of lists is concatenated.
For Maybe
, both branches gets evaluated and if both are Just
the result of the one to which the arrow points is returned:
> Just 'a' *> Just 'b'
Just 'b'
> Nothing *> Just "foo"
Nothing
I find these operators most pleasant when it's applied in "stateful" applicatives, for example IO:
λ> reply <- putStr "Were you a good kid?" *> getLine <* putStrLn "HO! HO! HO!"
Were you a good kid?
yes
HO! HO! HO!
λ> reply
"yes"
or applicative regexps (via regex-applicative
library:
> "@username" =~ "@" *> many (psym isAlphaNum)
Just "username"
> let hashtag = "#" *> many (psym isAlphaNum)
> "#foo" =~ hashtag
Just "foo"
> let hashtags = many (few anySym *> hashtag <* few anySym)
> "Yo, #foo bar #baz, #quux" =~ hashtags
Just ["foo","baz,","quux"]
parsePrice :: String -> Maybe Double
parsePrice =
match $
optional ("$" <* optional " ") -- can start with dollar and optional space
*> (read <$> value) <* -- has a value in the middle
optional (optional " " *> "$") -- can end with dollar and optional space
where
value = do
dollars <- integral
cents <- optional fractional
pure (dollars ++ maybe "" ('.' :) cents)
integral = many digit
fractional = sym '.' *> replicateM 2 digit
digit = psym Char.isDigit
The "append" operator, <>
originates from Monoid
typeclass, but a more precise place for it is Semigroup
.
These days you can get it from Data.Semigroup
both in Haskell and PureScript.
class Semigroup a where
(<>) :: a -> a -> a
<>
combines two values of the same type. This "combine" operation can be pretty much anything as long as it's associative:
(x <> y) <> z = x <> (y <> z)
Haskell's Data.Semigroup
and purescript-monoid
defines a number of useful Semigroup
s.
The most natural usage is appending things in the common sense of most programming languages:
> "abc" <> "def"
"abcdef"
> [1..3] <> [4..6]
[1,2,3,4,5,6]
> Set.fromList [1..4] <> Set.fromList [3..6]
fromList [1,2,3,4,5,6]
> Map.fromList (zip [1..4] ["a".."d"]) <> Map.fromList (zip [3..6] ["C".."G"])
fromList [(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'E'),(6,'F')]
Note that in the last case values from the first Map
win over the values of the second Map
.
One might need to append two Map
s while merging the values under colliding keys. This can be done via Map.unionWith (<>)
.
Our favourite fizzbuzz could be written like that:
fizzbuzz :: Int -> String
fizzbuzz n = concat (fizz <> buzz <> num)
where
fizz, buzz, num :: [String]
fizz = "Fizz" <$ guard (n `mod` 3 == 0)
buzz = "Buzz" <$ guard (n `mod` 5 == 0)
num = show n <$ guard (null fizz && null buzz)
fizz
, buzz
and num
are simply lists of strings that are being appended together via <>
.
Given a Semigroup
for a
, we can get a free Semigroup
for Maybe a
:
class Semigroup a => Semigroup (Maybe a) where
Nothing <> Nothing = Nothing
Just a <> Nothing = a
Nothing <> Just b = b
Just a <> Just b = Just (a <> b)
This allows us to explicitly describe the "default" case in fizzbuzz:
fizzbuzz :: Int -> String
fizzbuzz n = fromMaybe (show n) (fizz <> buzz)
where
fizz, buzz :: Maybe String
fizz = "Fizz" <$ guard (n `mod` 3 == 0)
buzz = "Buzz" <$ guard (n `mod` 5 == 0)
Now fizz
and buzz
are Maybe String
, we append them to each other and if that is Nothing
we just return the default show n
.
For every Semigroup
b
, we can also get a free Semigroup
for a -> b
:
class Semigroup b => Semigroup (a -> b) where
f <> g = \a -> f a <> g a
The result of appending two functions is a function that passes a value to both and appends the results.
-- | Combines the gifts that should be sent to a given kid.
kidGift :: Kid -> Maybe Gift
kidGift = goodKidGift <> candyGift
-- | Good kids get requested gifts. Bad ones get nothing.
goodKidGift :: Kid -> Maybe Gift
goodKidGift kid =
requestedGift kid <$ isGoodKid kid
-- | We attach some fresh candies for every kid.
-- Bad ones get old uneatable candies and salmiaki (unless they are from Finland).
candyGift :: Kid -> Maybe Gift
candyGift kid =
(freshCandies <$ isGoodKid kid)
<>
(salmiakki <$ guard (not (isGoodKid kid) && not (fromFinland kid)))
<>
(oldCandies <$ guard (not (isGoodKid kid)))
As show above with Map.unionWith (<>)
it's common to pass <>
to other higher-order functions.
Some of those patterns are so common that there are shorthands for them.
fold
folds a structure and combines all elements:
> let fold = foldl (<>) mempty
> fold ["foo", "bar", "baz"]
"foobarbaz"
foldMap
allows to first apply a function to all of them:
> let foldMap f = fold . map f
> foldMap Product [1..10]
Product {getProduct = 3628800}
The "alt" operator (<|>) comes from Alternative
class. In PureScript, more precise Alt
class is available.
class Alt f where
(<|>) :: f a -> f a -> f a
It's very similar to the <>
operator, it also combines two values, only its defined for * -> *
"container" types.
It also has to be associative, but in addition to that it also must be distributive:
f <$> (x <|> y) == (f <$> x) <|> (f <$> y)
Applying a function to a combination of two values is the same as combining those values after applying the function to each of them.
<|>
works as append for lists/arrays:
> ['a'..'c'] <|> ['D'..'F']
"abcDEF"
That's an interesting fact, but <|>
is seldomly used like that.
For appending it's more common to use <>
.
<|>
is very handy for picking one of the Maybe
values:
> Nothing <|> Just 'a' <|> Just 'b'
Just 'a'
If you have many alternatives you might prefer to use asum
(Haskell) or oneOf
(PureScript):
> asum [ Nothing, Just 'a', Just 'b' ]
Alt comes handy for matching alternatives of regexps:
data Currency = EUR | GBP | USD
deriving (Show)
currency :: RE Char Currency
currency =
USD <$ ("$" <|> "USD")
<|>
EUR <$ ("€" <|> "EUR")
<|>
GBP <$ ("£" <|> "GBP")
Or even swallowing any IOException
in a hideous way (may be good for scripts):
> readFile "/etc/shadow" <|> pure "Ooops. Operation failed."
"Ooops. Operation failed."
Or race two async processes:
> runConcurrently $
Concurrently (threadDelay 5000 *> print 5000)
<|>
Concurrently (threadDelay 1000 *> print 1000)
1000
purescript-aff
steps in the game with examples from its README:
example = do
result <- Ajax.get "http://foo.com" <|> Ajax.get "http://bar.com"
pure result
-- Make a request with a 3 second timeout
example =
sequential $ oneOf
[ parallel (Just <$> Ajax.get "https://foo.com")
, parallel (Nothing <$ delay (Milliseconds 3000.0))
]
The bind
operation has two directional operators, >>=
and =<<
. This operators are so common that one of them became a part of Haskell logo.
As usually, I give a couple of examples that use some of our favourite monads:
Lists are concatenated like via concatMap
:
λ> show =<< [1..10]
"12345678910"
For Maybe
the Just
values are threaded through, short-circuting on Nothing
:
λ> Just "foobarbaz" >>= Text.stripPrefix "foo" >>= Text.stripSuffix "baz"
Just "bar"
Nested property access in json data:
(.?) :: forall a. DecodeJson a => JObject -> String -> Either String a
object .? "foo" >>= \foo -> foo .? "bar" >>= \bar -> bar .? "baz"
Though in this case it's much more readable to use do-sugar:
do foo <- object .? "foo"
bar <- foo .? "bar"
bar .? "baz"
You can use destructuring functions like maybe
or either
to conviniently match
in the binding function.
requestLaunchPermit >>= maybe abortLaunch (\permit -> igniteEngine (enginePermit permit))
Though using case
syntax (perhaps via LambdaCase
) is not bad and sometimes better as well:
requestLaunchPermit >>= \case
Nothing -> abortLaunch
Just permit -> igniteEngine (enginePermit permit)