<div dir="ltr"><div>Hello chums,</div><div><br></div><div>I've been playing around with an idea, something that has obvious pros</div><div>and cons, but I'll sell it to you because there might be some positive</div>
<div>ideas out of it. Consider the following operator:</div><div><br></div><div> {-# LANGUAGE TypeOperators, DataKinds, KindSignatures #-}</div><div><br></div><div> module Docs where</div><div><br></div><div> import GHC.TypeLits</div>
<div><br></div><div> type a ? (sym :: Symbol) = a</div><div><br></div><div>First I'll describe how I'd want to use this and then what I think</div><div>are the advantages and disadvantages.</div><div><br></div>
<div>I call this (?) operator “the documentation operator”, to be used for:</div><div><br></div><div>* Things that either don't belong or can't be encoded in the type</div><div> system, or for things need to be in English.</div>
<div>* Things that cannot be encoded in Haddock.</div><div><br></div><div>The simple case of ye olde days:</div><div><br></div><div> -- | Lorem ipsum dolor sit amet. Suspendisse lacinia nibh et</div><div> -- leo. Aenean auctor aliquam dapibus.</div>
<div> loremIpsum :: Int -> Int -> String</div><div><br></div><div>Which has since been somewhat evolved into:</div><div><br></div><div> loremIpsum :: Int -- ^ Lorem ipsum dolor sit amet.</div><div> -> Int -- ^ Suspendisse lacinia nibh et leo.</div>
<div> -> String -- ^ Aenean auctor aliquam dapibus.</div><div><br></div><div>But could now be written:</div><div><br></div><div> loremIpsum :: Int ? "Lorem ipsum dolor sit amet."</div><div>
-> Int ? "Suspendisse lacinia nibh et leo."</div><div> -> String ? "Aenean auctor aliquam dapibus."</div><div><br></div><div>Here is a contrived case I'll use later on:</div>
<div><br></div><div> data Person = Person</div><div><br></div><div> describeAge :: Int ? "an age" -> String ? "description of their elderliness"</div><div> describeAge n = undefined</div><div>
<br></div><div> personAge :: Person ? "a person" -> Int ? "their age"</div><div> personAge = undefined</div><div><br></div><div>One could also encode previously informal specifications more formally,</div>
<div>so that</div><div><br></div><div> -- | The action 'hFlush' @hdl@ causes any items buffered for output</div><div> -- in handle @hdl@ to be sent immediately to the operating system.</div><div> --</div>
<div> -- This operation may fail with:</div><div> --</div><div> -- * 'isFullError' if the device is full;</div><div> --</div><div> -- * 'isPermissionError' if a system resource limit would be exceeded.</div>
<div> -- It is unspecified whether the characters in the buffer are discarded</div><div> -- or retained under these circumstances.</div><div> hFlush :: Handle -> IO ()</div><div> hFlush handle = wantWritableHandle "hFlush" handle flushWriteBuffer</div>
<div><br></div><div>with</div><div><br></div><div>type Throws ex (docs :: Symbol) = docs</div><div><br></div><div>could now be written</div><div><br></div><div> hFlush :: Handle ? "flush buffered items for output on this handle" -> IO ()</div>
<div> ? Throws IsFullError "if the device is full"</div><div> ? Throws IsPermissionError</div><div> "if a system resource limit would be exceeded. It is \</div><div> \unspecified whether the characters in the buffer are \</div>
<div> \discarded or retained under these circumstances."</div><div> hFlush handle = wantWritableHandle "hFlush" handle flushWriteBuffer</div><div><br></div><div>With this in place, in GHCi you get documentation "lookup" for free:</div>
<div><br></div><div> > :t hFlush</div><div> hFlush</div><div> :: (Handle ? "flush buffered items for output on this handle")</div><div> -> (IO () ? Throws IsFullError "if the device is full")</div>
<div> ? Throws</div><div> IsPermissionError</div><div> "if a system resource limit would be exceeded. It is unspecified</div><div> whether the characters in the buffer are discarded or retained</div>
<div> under these circumstances."</div><div><br></div><div>And you get function composition, or “documentation composition” for free:</div><div><br></div><div> > :t describeAge . personAge</div>
<div> describeAge . personAge</div><div> :: (Person ? "a person")</div><div> -> String ? "description of their elderliness"</div><div><br></div><div>We could have a :td command to print it with docs, and otherwise docs</div>
<div>could be stripped out trivially by removing the ? annotations:</div><div><br></div><div> > :t describeAge . personAge</div><div> describeAge . personAge</div><div> :: Person -> String</div><div> > :td describeAge . personAge</div>
<div> describeAge . personAge</div><div> :: (Person ? "a person")</div><div> -> String ? "description of their elderliness"</div><div><br></div><div>You could even add clever printing of such “documentation types”:</div>
<div><br></div><div> > :t hFlush</div><div> hFlush</div><div> :: Handle — flush buffered items for output on this handle</div><div> -> IO ()</div><div> Throws IsFullError if the device is full"</div>
<div> Throws IsPermissionError if a system resource limit would be</div><div> exceeded. It is unspecified whether the characters in the buffer</div><div> are discarded or retained under these circumstances."</div>
<div><br></div><div>Unfortunately it doesn't work with monadic composition, of course.</div><div><br></div><div>So here are the advantages:</div><div><br></div><div>* You get parsing for free (and anyone using haskell-src-exts).</div>
<div>* You get checking for free (i.e. GHC can check that IsFullError exists</div><div> for you).</div><div>* You get a continuity of documentation through your operations</div><div> including composition.</div><div>* You can extend the "documentation language" easily by just defining</div>
<div> some types (like the Throws I used above). SeeMore, Author,</div><div> Deprecated, etc. Whatever.</div><div>* You can print out some helpful looking documentation in GHCi based on</div><div> these simple types.</div>
<div>* There's no longer this informal "it might throw this exception" kind</div><div> of pros we're forced to write.</div><div>* It could also be used for annotations other than pure documentation,</div>
<div> including testing. E.g. add a Testable "property" and then your test</div><div> framework can search for functions with this Testable annotation.</div><div>* Free of Haddock's syntax.</div><div><br>
</div>
<div>Here are the disadvantages:</div><div><br></div><div>* It doesn't work for types.</div><div>* Writing big pros inside a string can be boring without a decent</div><div> editor.</div><div>* The neat composition trick only goes so far.</div>
<div>* There might be a compilation overhead.</div><div>* It would require an updated GHCi to strip them out when not wanted.</div><div>* Requires GHC 7.6.1+.</div><div><br></div><div>Conclusions:</div><div><br></div><div>
What we have now for documentation is pretty good, especially generated</div><div>documentation. Compared to other languages Haskell is quite well</div><div>documented, I feel. But we can do more with it. In some languages,</div>
<div>documentation is built into the language. You can ask for documentation</div><div>inside the REPL, it belongs to that piece of code. It shouldn't, I don't</div><div>think, be left as a code comment which is essentially whitespace as far</div>
<div>as the compiler is concerned.</div><div><br></div><div>Two sweet ideas that I like from the above are:</div><div><br></div><div>* The checking by GHC.</div><div>* The extension of the "documentation language", with the ability to</div>
<div> formalize things like what exceptions are thrown.</div><div>* Composing functions generates "new" documentation that still makes</div><div> sense.</div><div><br></div><div>Thoughts?</div><div><br></div>
<div>
Ciao!</div></div>