Search code examples
haskelltestingarchitecturecabalcabal-install

Leave free access to internal intermediary functions in Haskell library?


I'm writing a numerical optimisation library in Haskell, with the aim of making functions like a gradient descent algorithm available for users of the library. In writing these relatively complex functions, I write intermediary functions, such as a function that performs just one step of gradient descent. Some of these intermediary functions perform tasks that no user of the library could ever have need for. Some are even quite cryptic, but make sense when used by a bigger function.

Is it common practice to leave these intermediary functions available to library users? I have considered moving these to an "Internal" library, but moving small functions into a whole different library from the main functions using them seems like a bad idea for code legibility. I'd also quite like to test these smaller functions as well as the main functions for debugging purposes down the line - and ideally would like to test both in the same place, so that complicates things even more.

I'm unsurprisingly using Cabal for the library so answers in that context as well would be helpful if that's easier.


Solution

  • You should definitely not just throw such internal functions in the export of your package's trunk module, together with the high-level ones. It makes the interface/haddocks hard to understand, and also poses problems if users come to depend on low-level details that may easily change in future releases.

    So I would keep these functions in an “internal” module, which the “public” module imports but only re-exports those that are indended to be used:

    • Public

      module Numeric.Hegash.Optimization (optimize) where
      import Numeric.Hegash.Optimization.Internal
      
    • Private

      module Numeric.Hegash.Optimization.Internal where
      
      gradientDesc :: ...
      gradientDesc = ...
      
      optimize :: ...
      optimize = ... gradientDesc ...
      

    A more debatable matter is whether you should still allow users to load the Internal module, i.e. whether you should put it in the exposed-modules or other-modules section of your .cabal file. IMO it's best to err on the “exposed” side, because there could always be valid use cases that you didn't foresee. It also makes testing easier. Just ensure you clearly document that the module is unstable. Only functions that are so deeply in the implementation details that they are basically impossible to use outside of the module should not be exposed at all.