Improve documentation, reduce duplication and add examples to haddock
I've successfully setup doctests for a few of my projects, here is one such example:
https://github.com/lehins/massiv/blob/3d5c093abfa04119c9bc100758cae6de374e6f07/massiv/massiv.cabal#L104-L116
Adding examples to haddock that are actually checked during CI brings enormous value not only to the end user, but to overall quality of a project.
I suggest adding small examples to each of the functions in:
Data.VectorData.Vector.PrimtiveData.Vector.StorableData.Vector.UnboxedData.Vector.MutableData.Vector.Primtive.MutableData.Vector.Storable.MutableData.Vector.Unboxed.Mutable
Granted, there will be repetition, but it doesn't come without great value. Each example would act as a small unit test for each particular vector type, in fact, all of them would execute different code paths.
At first sight this might look like an approach that increases haddock duplication, but there is a second part to it. Documentation itself for each of the functions in above modules should be minimal, with a link to it's counterpart in the Generic modules. As to functions in Data.Vector.Generic and Data.Vector.Generic.Mutable, their documentation should be expanded describing all of the quirks. unliftio is a great example of where this approach works extremely well, eg. createDirectory. They can't contain doctests without choosing one of the four representations, so it might not be as beneficial to put examples there, but potentially linking back to monomorphic variants instead could solve that problem.
Here is a concrete example:
module Data.Vector.Primitive where
...
-- | /O(1)/ First element. See `G.head` for more info.
--
-- ====__Examples__
--
-- >>> import Data.Vector.Primitive as VP
-- >>> VP.head $ VP.fromList [1,2,3,4::Int]
-- 1
--
head :: Prim a => Vector a -> a
{-# INLINE head #-}
head = G.head
module Data.VEctor.Generic where
...
-- | /O(1)/ Extract the first element of a vector. This is a partial function and will
-- throw an error if the supplied vector is empty. Consider using a safer alternative
-- @(v `!?` 0)@. A monadic variant `headM` is also available.
--
-- ====__Examples__
--
-- For usage examples see:
--
-- * @Data.Vector.`Data.Vector.head`@
-- * @Data.Vector.Primitive.`Data.Vector.Primitive.head`@
-- * @Data.Vector.Storable.`Data.Vector.Storable.head`@
-- * @Data.Vector.Unboxed.`Data.Vector.Unboxed.head`@
--
head :: Vector v a => v a -> a
{-# INLINE_FUSED head #-}
head v = v ! 0
This whole suggestion results in two nicely documented functions with interlinking between each other:

and clicking on the link we get to version of head for Primitive vector:

If that is something that we all can agree on I'll volunteer to get the ball rolling an will setup a branch with at least a few functions fully documented so other can pitch in by example.
Please, guys, let me know what you think!
on the face of it, this sounds FANTASTIC
@RyanGlScott whats current best practices for doctest engineering in the cabal v3 universe?
The way I usually do it, I use Oleg's cabal-doctest approach of custom Setup.hs:
https://github.com/lehins/massiv/blob/master/massiv/Setup.hs
and a very simple binary: https://github.com/lehins/massiv/blob/master/massiv/tests/doctests.hs
Works like a charm.
I think it's big improvement over status quo and strongly for it. However it suspectible to copy-paste errors. For example:
module Data.Vector.Primitive where
...
-- >>> import Data.Vector.Primitive as VP
-- >>> VP.head $ VP.fromList [1,2,3,4::Int]
-- 1
--
head :: Prim a => Vector a -> a
module Data.Vector.Storable where
...
-- >>> import Data.Vector.Primitive as VP <----- HERE
-- >>> VP.head $ VP.fromList [1,2,3,4::Int]
-- 1
--
head :: Storable a => Vector a -> a
Doctests won't catch such mistakes since example works fine, it's just wrong example. One way to prevent such problems is to add some grep-based contraption to check that examples import from correct module
Or it's possible to improve haddock. What we essentially want is to create haddock according to a template/copy documentation from Generic version
i wonder how backpack would come into play here ... 🤔
or CPP or template haskell
@cartazio It would be good if you could find out. I don't want to do a whole bunch of work for nothing.
I am planning on getting the initial stage of this done this upcoming weekend.
CPP or template-haskell should be really avoided here! We want people to contribute and be able to read the code and documentation, not wonder how this haddock came to being.
TH should be avoided for another reason as well. It's broken for cross-compiling and effectively it means that nothing could be cross-compiled
agreed on all fronts. CPP beyond conditional ifdefs.
and any TH tend to be contributor hostile. well, everyone hostile, and build time evil.
i'll start my experiments later this week or next weekend, day job and all those things :)
At this point we have working doctests but not all functions have examples so PRs with more are welcom