See a typo? Have a suggestion? Edit this page on Github
This is the first Haskell code kata I've put on this blog (to my knowledge). The idea is to present a self contained, relatively small coding challenge to solidify some skills with Haskell. If people like this and would like to see more, let me know. Caveat: these will almost certainly be supply driven. As I notice examples like this in my code, I'll try to extract them like this blog post.
OK, here's the story. The
filelock library provides a set of functions for working with locked files. Some of these will block until a file lock is available. However, some will instead return a
Maybe value and use
Nothing to represent the case where a lock is not available.
What's interesting about this is the
withTryFileLock function, which is a rare combination of the
bracket pattern and potential failure. Its signature is:
withTryFileLock :: FilePath -> SharedExclusive -> (FileLock -> IO a) -> IO (Maybe a)
FilePath parameter says which file to try and lock.
SharedExclusive says the type of lock to take. The third parameter is the action to perform with the file lock. That action will return an
IO a action. Then, if the lock is taken, that
a value ends up wrapped in a
Just constructor and returned from
withTryFileLock. If the lock failed, then
Nothing is returned.
The thing is, there's an alternative function signature we could have instead, which would provide a
Maybe FileLock to the inner action. It looks like this:
withTryFileLock :: FilePath -> SharedExclusive -> (Maybe FileLock -> IO a) -> IO a
Why would you want one versus the other? It's not the topic I'm focusing on today, and it honestly doesn't matter that much. Here's the code kata:
Implement the second version in terms of the first, and the first version in terms of the second.
To complete these code kata:
- Copy/paste the code snippet below into a file called
- Make sure you have Stack installed.
- Make tweaks to
- If you get an error in step 4, go back to 3.
- Congratulations, you've successfully fixed the program and parsed my BASIC-esque goto statement!
Bonus points: generalize
version2 to work in any
#!/usr/bin/env stack -- stack --resolver lts-14.1 script import System.FileLock (FileLock, SharedExclusive (..), withTryFileLock) -- We've imported this function: -- -- withTryFileLock -- :: FilePath -- -> SharedExclusive -- -> (FileLock -> IO a) -- -> IO (Maybe a) -- | Implement this function by using the 'withTryFileLock' imported above. version1 :: FilePath -> SharedExclusive -> (Maybe FileLock -> IO a) -> IO a version1 = _ -- | And now turn it back into the original type signature. Use the -- 'version1' function we just defined above. version2 :: FilePath -> SharedExclusive -> (FileLock -> IO a) -> IO (Maybe a) version2 = _ -- | Just a simple test harness main :: IO () main = do version1 "version1.txt" Exclusive $ \(Just _lock) -> version1 "version1.txt" Exclusive $ \Nothing -> putStrLn "Yay, it worked!" Just _ <- version2 "version2.txt" Exclusive $ \_lock -> do Nothing <- version2 "version2.txt" Exclusive $ error "Should not be called" pure () putStrLn "Yay, it worked!"