# Superpowered keyword args in Haskell

## Overview

As programs get larger and more complex, the components therein also grow. This happens on several levels; we often have to deal with growing applications, packages, modules, and test suites. This blog post is about how to deal with the growth of the humble function.

If you’ve ever worked on a production codebase, you know that one function that lives in a module that several others depend on. It sits at the very core of your application, growing in complexity to support the ever-expanding needs of the business. It is the entrypoint into a sea of complexity…and it has several positional arguments that mean nothing without documentation and other context. It is a powerful and useful beast, but it must be tamed.

## Domain

Maybe you work on an information system for posts of some kind where a common operation is fetching a set of posts. Various filters/sorts need to be applied conditionally based on whatever the caller needs:

• Filter - Posted by a certain person
• Filter - Posted before or after a certain date
• Filter - Liked by a particular other user
• Filter - Commented on by a particular other user
• Filter - Accessible to a particular user
• Filter - Public only
• Sort - Popular posts (top n posts ordered by likes)
• Sort - Most controversial posts (top n posts ordered by number of comments)

## A function

The type signature for the function that does this type of fetch could look something like this:

newtype UserId = UserId Integer
data TimeSpan = TimeSpan { start :: UTCTime, end :: UTCTime }
data Sort = Popular | Controversial | MostLinked

fetchPosts
=> Maybe UserId
-> Maybe TimeSpan
-> Maybe UserId
-> Maybe UserId
-> Maybe UserId
-> Bool
-> Sort
-> m [Post]
fetchPosts userId timeSpan likedBy commentedOnBy accessibleBy publicOnly sort
= -- omitted


This works, but is very error prone. Even though we’re making use of newtype wrappers, the positional arguments could easily get mixed up, causing, for instance, a fetch for posts likedBy a user instead of the expected posts commentedOnBy by a user. Every time a call to this function is made, you have to be careful to provide the correct arguments in the correct order.

## A safer approach

One way to improve this situation is to make extensive use of newtype wrappers and avoid Boolean Blindness by utilizing more specific Sum Types. This is a pretty common idiom, and it helps a bit:

data VisibilityFilter = PublicOnly | PublicOrPrivate
newtype AuthorId = AuthorId UserId -- and a bunch more, you get the picture

fetchPosts
=> Maybe AuthorId
-> Maybe TimeSpan
-> Maybe LikedBy
-> Maybe CommentedOnBy
-> Maybe AccessibleBy
-> VisibilityFilter
-> Sort
-> m [Post]
fetchPosts userId timeSpan likedBy commentedOnBy accessibleBy publicOnly sort
= -- ...


This is safer, but arguably more annoying to work with due to all the manual packing of newtypes that needs to be done, and every call is pretty noisy. Consider what fetching all public posts for a given author would look like:

main = do
fetchPosts $defaultFetchPostsArgs { authorId = UserId 1 } -- ... do something with posts  not only is that easier to write, but it also lets us explicitly specify what we want to change about the general functionality (“fetch all posts”) without needing to explicitly specify what we don’t care about changing (all other filters and sort methods) worth noting that RecordWildCards is utilized here to retain exactly the same argument names as before, so the function body would not need to change. ## Adding functionality suppose we now want to not only filter by a single user, but we also want to search for posts that contain certain tags. We could modify FetchPostsArgs to support this case: data FetchPostsArgs = { authorId :: Maybe UserId , timeSpan :: Maybe TimeSpan , likedBy :: Maybe UserId , commentedOnBy :: Maybe UserId , accessibleBy :: Maybe UserId , visibilityFilter :: VisibilityFilter , sort :: Sort , tags :: [Tag] } defaultFetchPostsArgs :: FetchPostsArgs defaultFetchPostsArgs = FetchPostsArgs { authorId = Nothing , timeSpan = Nothing , likedBy = Nothing , commentedOnBy = Nothing , accessibleBy = Nothing , visibilityFilter = Nothing , sort = SortPopular , tags = [] }  and update the body of fetchPosts as needed. ## Utilizing Booleans and Smart Constructors fetchPostArgs looks a lot like a Monoid, and we can certainly frame it that way, with a little bit of finagling: data Visibility = Public | Private | Protected data FetchPostsArgs = { authorId :: Last UserId , timeSpan :: Last TimeSpan , likedBy :: Last UserId , commentedOnBy :: Last UserId , accessibleBy :: Last UserId , forbiddenVisibilities :: [Visibility] , sorts :: [Sort] , tags :: [Tag] } defaultFetchPostsArgs :: FetchPostsArgs defaultFetchPostsArgs = FetchPostsArgs { authorId = Last Nothing , timeSpan = Last Nothing , likedBy = Last Nothing , commentedOnBy = Last Nothing , accessibleBy = Last Nothing , forbiddenVisibilities = [] , sorts = [] , tags = [] } instance Semigroup FetchPostsArgs where (<>) = -- ... pairwise <> instance Monoid FetchPostsArgs where mempty = -- ... every field is mempty  with this formulation, we can build up Smart Constructors and compose them to create more specific queries. For example, suppose we want to find all public posts by User 2 tagged “Haskell” and “Code” posted less than 7 days ago, that I (User 1) liked. Some Smart Constructors can help here: publicOnlyArgs :: FetchPostsArgs publicOnlyArgs = mempty { forbiddenVisibilities = [Private, Protected] } likedByArgs :: UserId -> FetchPostsArgs likedByArgs userId = mempty { likedBy = Last (Just userId) } authoredByArgs :: UserId -> FetchPostsArgs authoredByArgs userid = mempty { authorId = Last (Just userId) } taggedArgs :: Tag -> FetchPostsArgs taggedArgs tag = mempty { tags = [tag] } postedAfter :: UTCTime -> IO FetchPostsArgs postedAfter time = do now <- getCurrentTime pure$ mempty { timeSpan = Last (TimeSpan time now) }


and our fetch looks like:

main = do
sevenDaysAgo <- subtractDays 7 <$> getCurrentTime timeArgs <- postedAfter sevenDaysAgo posts <- runFetch$
fetchPosts
\$  timeArgs
<> publicOnlyArgs
<> authoredByArgs (UserId 2)
<> likedByArgs (UserId 1)
<> taggedArgs (Tag "Code")

-- ... do something with posts


Using Monoids gives us better composition of common operations in many cases. I might argue that taking this step is unnecessary for this particular example, but it is at least helpful for illustrative purposes and might come in handy depending on your use case.

### A small note

Suppose we were writing a function fetchPostsForUser instead, which always required a UserId to fetch posts for. In this case, it does make sense to pull that individual argument outside of the Args data type and provide it as a required parameter, e.g.:

fetchPostsForUser :: MonadFetch m => UserId -> FetchPostsArgs -> m [Post]


The conceptual reason for this is that fetchPostsForUser has no default behavior without a UserId, and we need default behavior for the Monoid to remain sensible.

## In Short

If you are dealing with functions that:

• Have many positional arguments
• Are called in a variety of ways to accomplish specific goals
• Have a well-behaved “default” operation, where the arguments simply specify refinements on that default operation

then the Keyword Args pattern might work for you.