A common pattern of verbal contracts with newtypes are smart constructors. Usually you define them in an *.Internal
module and then only expose safe functions in public modules.
Although, in the case of monoid homomorphisms it is not really possible to make a function that decides whether some function is a monoid homomorphism or not, so this approach cannot be made fully watertight. I could imagine that you could use property-based testing to at least reject functions that are clearly not homomorphisms, but even that sounds difficult.
I would say that a newtype with verbal warnings on unsafe functions is the easiest idiomatic way to do this.
Edit: Here’s an example of my property-based testing idea:
{-# LANGUAGE FlexibleContexts #-}
import Test.LeanCheck
newtype MonoidHomomorphism a b = MkMonoidHomomorhpism
{runMonoidHomomorphism :: a -> b}
mkMonoidHomomorphism ::
(Listable a, Show a, Monoid a, Monoid b, Eq b) =>
(a -> b) ->
Maybe (MonoidHomomorphism a b)
mkMonoidHomomorphism f
| f mempty == mempty
, holds 100 (\x y -> f x <> f y == f (x <> y)) =
Just (MkMonoidHomomorhpism f)
| otherwise = Nothing
Obvious downsides are:
- limited to types that implement the required classes, especially
Listable
- probably poor performance at runtime
- not actually a proof