Uninteresting fact: the expiration date of a credit card is printed as something like "02/20", but it is valid until the end of the month. Of course the last day of February could be 28 or 29 depending on year, which can make calculating this day painful. Luckily Purescript has a great library for handling dates. Though the strongly typed nature of these constructs—like all things Haskell—means we have to jump through some hoops to parse a plain string like "02/20" into a full `Date`

type.

Consider the signature of `canonicalDate`

.

```
canonicalDate :: Year -> Month -> Day -> Date
```

In order to create a `Date`

, we first require explicit `Year`

, `Month`

, and `Day`

enums. We can get those with `toEnum`

, which has the following signature.

```
toEnum :: forall a. BoundedEnum a => Int -> Maybe a
```

This requires an `Int`

, which is derived from a string with `fromString`

. It looks like

```
fromString :: String -> Maybe Int
```

Going from a string to a `Date`

means

- Splitting the expiration string into separate month and year fields.
- Turning those fields into
`Int`

. - Turning those
`Int`

values into`Month`

and`Year`

enums. - Getting a
`Day`

value from the`Month`

and`Year`

. - Generating a complete
`Date`

from`Day`

,`Month`

, and`Year`

. - Composing gracefully against
`Maybe`

, so that the parsing may short circuit to`Nothing`

if the process fails at any point.

It seems like a hassle, but I am pleased with the solution I came up with. The procedure below leverages a lot of cool concepts, which is why I found it worth documenting here.

```
module ExpiryDate where
import Prelude (($), (<*>), (<<<), (>=>))
import Control.Apply (lift3, lift2)
import Data.Date (Date, canonicalDate, lastDayOfMonth)
import Data.Enum (toEnum)
import Data.Int (fromString)
import Data.Maybe (Maybe)
import Data.Profunctor.Strong((&&&), (***))
import Data.Semigroup (append)
import Data.String.CodeUnits (take, takeRight)
import Data.Tuple (uncurry)
expiryDate :: String -> Maybe Date
expiryDate = toDate <<< toEnums <<< splitDate
where
splitDate = (append "20" <<< takeRight 2) &&& (take 2)
toEnums = (fromString >=> toEnum) *** (fromString >=> toEnum)
toDate = (uncurry $ lift3 canonicalDate) <*> (uncurry $ lift2 lastDayOfMonth)
```

It's by no means a simple function, but I do think its control flow is easily analyzed in three parts.

```
splitDate = (append "20" <<< takeRight 2) &&& (take 2)
```

`expiryDate`

is tacit, but you can see from its signature that it accepts one parameter: a string to parse. The expiration dates in the database I was accessing were actually of the form "0220", meaning it wasn't enough to simply call `split`

over a slash. `take`

and `takeRight`

gave me the strings needed for the month and year respectively. The latter needed a "20" appended to it to be parsed into a valid year.

```
> take 2 "0220"
"02"
> (append "20" <<< takeRight 2) "0220"
"2020"
```

This is all very elementary stuff, but it presented me with a problem; I needed to run both these strings through an independent series of transformations, but then have access to them simultaneously in order to derive a `Day`

, and ultimately a `Date`

. In any imperative language I would have performed these transforms on their own lines then accessed the result, but Haskell let me express this branching and merging in a single composition chain with a Bifunctor.

A Functor is a context with an `fmap`

procedure that lets one map functions against it. `Maybe`

, `List`

, etc: you probably know them well. A Bifunctor is a similar context with two such values. Think `Either`

, or a tuple. You can still `fmap`

into a Bifunctor, but this operation is biased to one value, like how the `fmap`

instance of `Either`

only modifies `Right`

. Bifunctors also boast a function `bimap`

, with the signature

```
bimap :: forall a b c d f. (a -> b) -> (c -> d) -> f a c -> f b d
```

This can map two separate functions into the context, each operating on their respective value. Needless to say, Bifunctors are very useful in representing branched computations.

`splitDate`

hinges on the `&&&`

(`fanout`

) operator.

```
(&&&) :: forall p a b c. Category p => Strong p => p a b -> p a c -> p a (Tuple b c)
```

The signature is intimidating, but it makes sense in the context of `splitDate`

if you imagine each `p`

as a function arrow itself.

```
(&&&) :: forall a b c. (a -> b) -> (a -> c) -> (a -> Tuple b c)
```

`fanout`

takes two functions `f`

and `g`

, and composes them into a single function that runs `f`

and `g`

against the same parameter, collecting these respective computations in a single tuple. This allowed me to capture the month and year portions of a string and perform a transformation exclusively on the latter part. Various other Bifunctor operations then made it trivial to access these computations individually or in tandem.

```
toEnums = (fromString >=> toEnum) *** (fromString >=> toEnum)
```

Each string had to be parsed into an `Int`

, which in turn had to be made into a `Month`

or `Year`

enum. Since `fromString`

and `toEnum`

both return `Maybe`

, it makes sense to perform a monadic bind between them.

```
toYear :: String -> Maybe Year
toYear x = fromString x >>= toEnum
> toYear "2020"
(Just (Year 2020))
> toYear "!"
Nothing
```

But since the entire function is one point-free composition, the bind operation `>>=`

had to be replaced by a Kleisli composition `>=>`

.

```
fromString >=> toEnum
```

This operates much like a normal function composition `<<<`

, but within a monadic context like `Maybe`

.

You can see this code phrase repeated twice in `toEnums`

, but each instance is not the same procedure; one `toEnum`

returns a `Maybe Year`

and the other a `Maybe Month`

. Much like with `pure`

, it is context-sensitive.

The `***`

(`splitStrong`

) operator can be considered a tuple-specific version of `bimap`

.

```
(***) :: forall a b c d. (a -> b) -> (c -> d) -> (Tuple a c) -> (Tuple b d)
```

It composes the separate Kleisli compositions into a single function that operates on the tuple of strings. This function yielded a new tuple of `Maybe Year`

and `Maybe Month`

and passed it along as input to the final composition.

```
toDate = (uncurry $ lift3 canonicalDate) <*> (uncurry $ lift2 lastDayOfMonth)
```

If you aren't having fun yet, you will here. I had to use the `Year`

and `Month`

values in the tuple twice: once to generate a `Day`

with `lastDayOfMonth`

, and then to make an actual `Date`

with `canonicalDate`

. Of course the ultimate function also required access to the output of `lastDayOfMonth`

. How did I compose with three parameters? A Trifunctor?

Consider the rightmost phrase first. `lastDayOfMonth`

gives a `Day`

. The order of the Bifunctor (year, month) finally makes sense when you examine its type signature.

```
lastDayOfMonth :: Year -> Month -> Day
```

Of course I provided `Maybe Year`

and `Maybe Month`

instead. That's where `lift2`

came in; it raises a dyadic function into a monadic context.

```
lift2 :: forall a b c f. Apply f => (a -> b -> c) -> f a -> f b -> f c
```

It is functionally equivalent to Applicative Functor usage like

```
f <$> x <*> y
```

but is nice and composable. `lift2`

can also take on another important role in tacit programming. I didn't make use of it in `expiryDate`

, but examine the signature (substituting for `f`

where need be) and see if you can figure it out.

`lift2`

expects two parameters, but it was provided a single Bifunctor. So I used `uncurry`

.

```
uncurry :: forall a b c. (a -> b -> c) -> Tuple a b -> c
```

It takes a dyadic function and turns it into a procedure that accepts a single tuple parameter. The phrase

```
uncurry $ lift2 lastDayOfMonth
```

is a function that uses the entire Bifunctor for a single operation with one output value, in contrast to the parallel processing I was doing earlier. It's easy enough to see how this produces a `Day`

.

The other phrase

```
(uncurry $ lift3 canonicalDate)
```

is applied across three `Maybe`

contexts: the aforementioned `Year`

and `Month`

, but also the `Day`

from `lastDayOfMonth`

. `lift3`

should be self-explanatory, but if you really need a visualization

```
canonicalDate <$> Maybe Year <*> Maybe Month <*> Maybe Day
```

should do it. `uncurry`

is again needed to "unwrap" the Bifunctor, though it is actually turning `lift3 canonicalDate`

into a dyadic function that requires a tuple and a `Day`

.

```
uncurry $ lift3 canonicalDate :: Tuple (Maybe Year) (Maybe Month) -> Maybe Day -> Maybe Date
```

In any case, the great question is still unanswered: how did I compose a function of three parameters, where the final argument was itself the output of a function? It would be easy if I had a J style hook like

```
hook :: forall a b c. (a -> b -> c) -> (a -> b) -> a -> c
hook f g = \x -> f x (g x)
```

This hook, also known as the S combinator, is a control flow pattern so common that it implicitly takes place in J whenever one pairs two functions next to one another. It doesn't appear in `expiryDate`

at first glance, but it's worth taking a look at `<*>`

.

```
(<*>) :: forall a b f. Apply f => f (a -> b) -> f a -> f b
```

Change all instances of `a`

to `b`

, and `b`

to `c`

.

```
(<*>) :: forall b c f. f (b -> c) -> f b -> f c
```

Change all occurrences of `f`

to a function `a ->`

. Remember that parentheses don't really matter in a curried language.

```
(<*>) :: forall a b c. (a -> b -> c) -> (a -> b) -> a -> c
```

It turns out that the Applicative instance of a function is a hook. Perfect. It was then really easy to run

```
(uncurry $ lift3 canonicalDate) (Maybe Year, Maybe Month) ((uncurry $ lift2 lastDayOfMonth) (Maybe Year, Maybe Month))
```

entirely tacitly, just like

```
f x (g x)
```

I now had an expiration date.

It took me 180 lines of English to explain six lines of code. All to parse a stupid date. "Map *this* up your functor and smoke it nerd", you might say. But I think the flow of the function body does wonders to mask the complexity of a convoluted process.

```
toDate <<< toEnums <<< splitString
```

About as plain as it can be; any intermediate variables would only be noise. Think of it as one big Unix shell pipe. Throw in a basic understanding of Bifunctors and it's really not that bad.

`lift2`

can be used as a J style monadic fork, where `fork`

is

```
fork :: (a -> b -> c) -> (d -> a) -> (d -> b) -> d -> c
fork f g h = \x -> f (g x) (h x)
```

Take a look at the signature again.

```
lift2 :: forall a b c f. Apply f => (a -> b -> c) -> f a -> f b -> f c
```

Change `f`

to `d ->`

this time and rearrange the parentheses as you please.

```
lift2 :: forall a b c d. (a -> b -> c) -> (d -> a) -> (d -> b) -> d -> c
```

See this brilliant discussion for more tacit insanity.