Step 1: Checkout and run Derive
The first step is to fetch the code with darcs:
$ darcs get http://community.haskell.org/~ndm/darcs/derive
Then follow the instructions in the README.txt file to regenerate the derivations locally and run the test suite:
$ cd derive $ ghci > :main --generate > :reload > :main --test
Like all my projects, Derive contains a .ghci file which sets up include paths and other useful utilities for working with each package. We first run :main --generate which autogenerates all the boilerplate in the Derive tool. We then run :reload to reload the freshly generated code, and finally :main --test which runs the test suite. In order to run the test suite it is likely that you will need to install some packages, see derive.html for a list of the likely packages.
Step 2: Add the Lens derivation
The first step when adding a derivation is to find a similar derivation (there are 35 existing derivations, so there is probably something reasonably close) and copy it. For the Lens derivation, it turns out that Ref is quite similar, so we copy Data/Derive/Ref.hs to Lens.hs and start modifying. There are several sections we need to tweak.
Firstly, we update the Haddock comments at the top of the file. These comments are purely for the users of our library, and should explain how to use the derivation.
Secondly, we modify the test section. This section is in comments just below the module header. We add import "data-lens" Data.Lens.Common then, working from the data types listed in Data/Derive/Internal/Test.hs, we modify the test to match what we expect as the output. This test section is tested with :main --test and contributes to the documentation (mainly the import line).
Thirdly, we modify the top-level names in this module, so the module name is Data.Derive.Lens and the main function is makeLens.
Step 3: Test and fix
When developing new derivations I follow test driven development. The tests are written, but the code has not been changed from the Ref derivation, so we expect the tests to fail. We follow the three standard steps in GHCi and :main --test complains with:
*** Exception: Results don't match! Expected: lensSpeed :: Lens Computer Int lensSpeed = lens speed (\ x v -> v{speed = x}) Got: refSpeed :: Ref Computer refSpeed = Ref{select = speed, update = \ f v -> v{speed = f (speed v)}}
As expected, the test fails, showing us that the copied Ref code does not do what we want the Lens code to do. Modifying the code, it takes a few minutes to arrive at:
lensSpeed :: Lens Computer lensSpeed = lens speed (\ x v -> v{speed = x})
The result is not quite right - the Int argument is missing from Lens, but so far we have been renaming and juggling existing code. Now we need to find the type of the field at hand, given the name of the field and the DataDecl of the type (DataDecl is from the haskell-src-exts library). Hunting around some of the Derive utility modules, particularly Language.Haskell, we can come up with:
typ = tyApps (tyCon "Lens") [dataDeclType d, fromBangType t] Just t = lookup field $ concatMap ctorDeclFields $ dataDeclCtors d
We rerun :main --test and the test passes. This command checks that the examples match, then checks that the result of the derivation type-checks for a larger range of examples. We have now added Lens derivations to Derive.
(If you are lucky, and your derivation can be added by example, then you might not have to write any code at all - simply modifying the test case automatically generates the right code. See the Eq type class for such an example.)
Step 4: Final test
While we have satisfied the test suite, it is always reassuring to run some tests by hand. Using the Example.hs file in the root of the repo we can try:
> :main Example.hs --derive=Lens
This command prints out the expected result:
lensMemory :: Lens Computer Int lensMemory = lens memory (\ x v -> v{memory = x}) lensSpeed :: Lens Computer Int lensSpeed = lens speed (\ x v -> v{speed = x}) lensWeight :: Lens Computer Int lensWeight = lens weight (\ x v -> v{weight = x})
Step 5: Send upstream
Before sending a patch, update CHANGES.txt with a one-line summary of what you have done, then you should see:
$ darcs add Data\Derive\Lens.hs $ darcs w -s M ./CHANGES.txt +1 M ./Data/Derive/All.hs -1 +2 A ./Data/Derive/Lens.hs M ./derive.cabal +1 M ./derive.htm +1
Create a patch (using the standard darcs workflow) and send it to me. The more derivations the merrier.
No comments:
Post a Comment