Modifying a Library!
Sometime last year, I wrote an article about advanced Stack techniques. We discussed one hypothetical case where a library had a bug. The solution here was to fork the repository and use a Github location in stack.yaml
.
I recently came across an easy example of doing this and thought I'd share it. We can see how to incorporate the change without stressing about its complexity. Even if you're only a beginner, this is a good skill to learn now!
You'll need some background on Stack first though! If you've never used Stack before, take a look at our Stack mini-course to learn more!
Background
I've been doing a bit of work lately with the persistent-migration
library. This library lets us set up manual migrations for a Persistent schema we've set up through template Haskell. See this article for more on that topic.
The migrations library has a couple functions that let us run some SQL operations. They live in the SqlPersistT
monad, as we would expect. However, something's a little off about them:
getMigration ::
MigrateSettings -> Migration -> SqlPersistT IO [MigrateSql]
runMigration ::
MigrateSettings -> Migration -> SqlPersistT IO ()
We can see that these functions restrict us to using SqlPersistT
on top of the normal IO
monad. But in most cases with Persistent, I use the withPostgresqlConn
function. This adds a MonadLogger
constraint. Thus IO
doesn't cut it. Most functions have a type signature looking like this:
databaseOp :: SqlPersistT (LoggingT IO) a
So we can't interoperate as easily as we'd like between our normal database operations, and these migration functions. The solution is that the migration functions should be more general in what monads they can use. This will be an easy fix, as we'll see. But first, we have to find a way to get our own version of the code.
Getting Started
The persistent-migration
library is on Github here. So we can make a fork of the repository, and clone that to our machine:
git clone https://github.com/jhb563/peristent-migration
Now we'll follow the build instructions in the Developing.md
file to get set up. Some of the tests fail on my machine, but we'll ignore that for this article.
Making Our Code Fixes
The code fixes turn out to be very easy. We can go into the relevant module and change the type signatures so they are more general:
module Database.Persistent.Migration.Postgres where
...
getMigration :: (MonadIO m) =>
MigrateSettings -> Migration -> SqlPersistT m [MigrateSql]
runMigration :: (MonadIO m) =>
MigrateSettings -> Migration -> SqlPersistT m ()
Even in the worst case, the only changes we would need to make here would be to add liftIO
calls in various places. But it turns out that this change doesn't break anything! The library still builds, and all the tests that were passing before still pass.
So now we can commit this change to our fork and push it to the repository.
Incorporating the Fix
Now we have to use our own fork as an alternative to the version of the library on Hackage. Before, the extra-deps section in our
stack.yaml` looked like this:
extra-deps:
- persistent-migration-0.1.0
This indicates we would grab the code from Hackage. But now we can use an alternative package format to reference our Github repository. Here's what we change it to:
extra-deps:
- git: https://github.com/jhb563/persistent-migration.git
commit: 9f9c5035efe
And now we've got our own fork as a dependency for our project! We can write code like so:
doMigrations :: SqlPersistT (LoggingT IO) a
doMigrations = do
runMigration defaultSettings migration
...
And everything works! Our code builds!
Potential Issues
Now, an approach like this can lead to some possible issues. We're now disconnected from the original repository. So if there was a new release, we'd have to do a bit more work to pull those changes into our own repository. Still, this isn't too difficult. One solution to this is to submit a pull request with our changes. If it gets accepted, they'll be in the next release! Then we can go back to using the version on Hackage!
Conclusion
In this article, we did a quick overview of how to make our own changes to libraries. We cloned the repository, made a code change, and added our fork as a dependency. Obviously, most of the changes you'll want to make aren't as simple as this one was. But it's good to use an example where all we're doing is tackling the issue of getting the code into our code base!
For a broad overview of how to use the Stack program, make sure to check out of Stack Mini-course! If you've never written any Haskell before, you can also look at our Beginners Checklist!